diff --git a/.travis.yml b/.travis.yml index 979a61bed..2052a4fd6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,9 @@ go_import_path: github.com/minio/minio sudo: required +services: + - docker + dist: trusty language: go @@ -13,12 +16,25 @@ env: - ARCH=i686 script: +## Run all the tests - make -- make test GOFLAGS="-timeout 20m -race -v" +- make test GOFLAGS="-timeout 15m -race -v" - make coverage +# Refer https://blog.hypriot.com/post/setup-simple-ci-pipeline-for-arm-images/ +# push image +- > + if [ "$TRAVIS_BRANCH" == "master" ] && [ "$TRAVIS_PULL_REQUEST" == "false" ] && [ "$ARCH" == "x86_64" ]; then + docker run --rm --privileged multiarch/qemu-user-static:register --reset + docker build -t minio/minio:edge-armhf . -f Dockerfile.armhf + docker build -t minio/minio:edge-aarch64 . -f Dockerfile.aarch64 + docker login -u="$DOCKER_USER" -p="$DOCKER_PASS" + docker push minio/minio:edge-armhf + docker push minio/minio:edge-aarch64 + fi + after_success: - bash <(curl -s https://codecov.io/bash) go: -- 1.7.4 +- 1.7.5 diff --git a/Dockerfile b/Dockerfile index b37ca0d04..4dbfc540c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,16 +1,18 @@ -FROM golang:1.7-alpine +FROM alpine:3.5 -WORKDIR /go/src/app +ENV GOPATH /go +ENV PATH $PATH:$GOPATH/bin +ENV CGO_ENABLED 0 -COPY . /go/src/app +WORKDIR /go/src/github.com/minio/ -RUN \ - apk add --no-cache git && \ - go-wrapper download && \ - go-wrapper install -ldflags "-X github.com/minio/minio/cmd.Version=2017-02-16T01:47:30Z -X github.com/minio/minio/cmd.ReleaseTag=RELEASE.2017-02-16T01-47-30Z -X github.com/minio/minio/cmd.CommitID=3d98311d9f4ceb78dba996dcdc0751253241e697" && \ - mkdir -p /export/docker && \ - rm -rf /go/pkg /go/src && \ - apk del git +RUN \ + apk add --no-cache ca-certificates && \ + apk add --no-cache --virtual .build-deps git go musl-dev && \ + go get -v -d github.com/minio/minio && \ + cd /go/src/github.com/minio/minio && \ + go install -v -ldflags "$(go run buildscripts/gen-ldflags.go)" && \ + rm -rf /go/pkg /go/src /usr/local/go && apk del .build-deps EXPOSE 9000 ENTRYPOINT ["minio"] diff --git a/Dockerfile.aarch64 b/Dockerfile.aarch64 new file mode 100644 index 000000000..e92905ecc --- /dev/null +++ b/Dockerfile.aarch64 @@ -0,0 +1,19 @@ +FROM resin/aarch64-alpine:3.5 + +ENV GOPATH /go +ENV PATH $PATH:$GOPATH/bin +ENV CGO_ENABLED 0 + +WORKDIR /go/src/github.com/minio/ + +RUN \ + apk add --no-cache ca-certificates && \ + apk add --no-cache --virtual .build-deps git go musl-dev && \ + go get -v -d github.com/minio/minio && \ + cd /go/src/github.com/minio/minio && \ + go install -v -ldflags "$(go run buildscripts/gen-ldflags.go)" && \ + rm -rf /go/pkg /go/src /usr/local/go && apk del .build-deps + +EXPOSE 9000 +ENTRYPOINT ["minio"] +VOLUME ["/export"] diff --git a/Dockerfile.armhf b/Dockerfile.armhf new file mode 100644 index 000000000..6fab9a5ce --- /dev/null +++ b/Dockerfile.armhf @@ -0,0 +1,19 @@ +FROM resin/armhf-alpine:3.5 + +ENV GOPATH /go +ENV PATH $PATH:$GOPATH/bin +ENV CGO_ENABLED 0 + +WORKDIR /go/src/github.com/minio/ + +RUN \ + apk add --no-cache ca-certificates && \ + apk add --no-cache --virtual .build-deps git go musl-dev && \ + go get -v -d github.com/minio/minio && \ + cd /go/src/github.com/minio/minio && \ + go install -v -ldflags "$(go run buildscripts/gen-ldflags.go)" && \ + rm -rf /go/pkg /go/src /usr/local/go && apk del .build-deps + +EXPOSE 9000 +ENTRYPOINT ["minio"] +VOLUME ["/export"] diff --git a/Makefile b/Makefile index f9eb341fa..9c5127ef7 100644 --- a/Makefile +++ b/Makefile @@ -71,10 +71,8 @@ verifiers: vet fmt lint cyclo spelling vet: @echo "Running $@:" - @go tool vet -all ./cmd - @go tool vet -all ./pkg - @go tool vet -shadow=true ./cmd - @go tool vet -shadow=true ./pkg + @go vet github.com/minio/minio/cmd/... + @go vet github.com/minio/minio/pkg/... fmt: @echo "Running $@:" diff --git a/appveyor.yml b/appveyor.yml index 68a87aa07..5a87b661a 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -11,11 +11,12 @@ clone_folder: c:\gopath\src\github.com\minio\minio # Environment variables environment: + GOROOT: c:\go17 GOPATH: c:\gopath # scripts that run after cloning repository install: - - set PATH=%GOPATH%\bin;c:\go\bin;%PATH% + - set PATH=%GOPATH%\bin;c:\go17\bin;%PATH% - go version - go env - python --version @@ -35,9 +36,9 @@ test_script: # Unit tests - ps: Add-AppveyorTest "Unit Tests" -Outcome Running - mkdir build\coverage - - go test -timeout 20m -v -race github.com/minio/minio/cmd... - - go test -v -race github.com/minio/minio/pkg... - - go test -timeout 15m -coverprofile=build\coverage\coverage.txt -covermode=atomic github.com/minio/minio/cmd + - go test -timeout 17m -race github.com/minio/minio/cmd... + - go test -race github.com/minio/minio/pkg... + - go test -coverprofile=build\coverage\coverage.txt -covermode=atomic github.com/minio/minio/cmd - ps: Update-AppveyorTest "Unit Tests" -Outcome Passed after_test: diff --git a/browser/app/index.js b/browser/app/index.js index d750577bc..26a1cc1d3 100644 --- a/browser/app/index.js +++ b/browser/app/index.js @@ -1,5 +1,5 @@ /* - * Minio Browser (C) 2016 Minio, Inc. + * Minio Cloud Storage (C) 2016 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/browser/app/js/__tests__/jsonrpc-test.js b/browser/app/js/__tests__/jsonrpc-test.js index 341d0c286..7e5b52f76 100644 --- a/browser/app/js/__tests__/jsonrpc-test.js +++ b/browser/app/js/__tests__/jsonrpc-test.js @@ -1,5 +1,5 @@ /* - * Minio Browser (C) 2016 Minio, Inc. + * Minio Cloud Storage (C) 2016 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/browser/app/js/actions.js b/browser/app/js/actions.js index c9b2f72bf..a39113dc6 100644 --- a/browser/app/js/actions.js +++ b/browser/app/js/actions.js @@ -1,5 +1,5 @@ /* - * Minio Browser (C) 2016 Minio, Inc. + * Minio Cloud Storage (C) 2016 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,13 +14,9 @@ * limitations under the License. */ -import url from 'url' import Moment from 'moment' import browserHistory from 'react-router/lib/browserHistory' -import web from './web' -import * as utils from './utils' import storage from 'local-storage-fallback' - import { minioBrowserPrefix } from './constants' export const SET_WEB = 'SET_WEB' @@ -28,7 +24,6 @@ export const SET_CURRENT_BUCKET = 'SET_CURRENT_BUCKET' export const SET_CURRENT_PATH = 'SET_CURRENT_PATH' export const SET_BUCKETS = 'SET_BUCKETS' export const ADD_BUCKET = 'ADD_BUCKET' -export const ADD_OBJECT = 'ADD_OBJECT' export const SET_VISIBLE_BUCKETS = 'SET_VISIBLE_BUCKETS' export const SET_OBJECTS = 'SET_OBJECTS' export const SET_STORAGE_INFO = 'SET_STORAGE_INFO' @@ -57,6 +52,9 @@ export const SET_SHARE_OBJECT = 'SET_SHARE_OBJECT' export const DELETE_CONFIRMATION = 'DELETE_CONFIRMATION' export const SET_PREFIX_WRITABLE = 'SET_PREFIX_WRITABLE' export const REMOVE_OBJECT = 'REMOVE_OBJECT' +export const CHECKED_OBJECTS_ADD = 'CHECKED_OBJECTS_ADD' +export const CHECKED_OBJECTS_REMOVE = 'CHECKED_OBJECTS_REMOVE' +export const CHECKED_OBJECTS_RESET = 'CHECKED_OBJECTS_RESET' export const showDeleteConfirmation = (object) => { return { @@ -78,11 +76,12 @@ export const hideDeleteConfirmation = () => { } } -export const showShareObject = url => { +export const showShareObject = (object, url) => { return { type: SET_SHARE_OBJECT, shareObject: { - url: url, + object, + url, show: true } } @@ -98,15 +97,17 @@ export const hideShareObject = () => { } } -export const shareObject = (object, expiry) => (dispatch, getState) => { +export const shareObject = (object, days, hours, minutes) => (dispatch, getState) => { const {currentBucket, web} = getState() let host = location.host let bucket = currentBucket if (!web.LoggedIn()) { - dispatch(showShareObject(`${host}/${bucket}/${object}`)) + dispatch(showShareObject(object, `${host}/${bucket}/${object}`)) return } + + let expiry = days * 24 * 60 * 60 + hours * 60 * 60 + minutes * 60 web.PresignedGet({ host, bucket, @@ -114,7 +115,11 @@ export const shareObject = (object, expiry) => (dispatch, getState) => { expiry }) .then(obj => { - dispatch(showShareObject(obj.url)) + dispatch(showShareObject(object, obj.url)) + dispatch(showAlert({ + type: 'success', + message: `Object shared. Expires in ${days} days ${hours} hours ${minutes} minutes.` + })) }) .catch(err => { dispatch(showAlert({ @@ -304,13 +309,13 @@ export const listObjects = () => { marker: marker }) .then(res => { - let objects = res.objects + let objects = res.objects if (!objects) objects = [] objects = objects.map(object => { - object.name = object.name.replace(`${currentPath}`, ''); - return object - }) + object.name = object.name.replace(`${currentPath}`, ''); + return object + }) dispatch(setObjects(objects, res.nextmarker, res.istruncated)) dispatch(setPrefixWritable(res.writable)) dispatch(setLoadBucket('')) @@ -344,9 +349,9 @@ export const selectPrefix = prefix => { if (!objects) objects = [] objects = objects.map(object => { - object.name = object.name.replace(`${prefix}`, ''); - return object - }) + object.name = object.name.replace(`${prefix}`, ''); + return object + }) dispatch(setObjects( objects, res.nextmarker, @@ -410,6 +415,25 @@ export const setLoginError = () => { } } +export const downloadSelected = (url, req, xhr) => { + return (dispatch) => { + xhr.open('POST', url, true) + xhr.responseType = 'blob' + + xhr.onload = function(e) { + if (this.status == 200) { + dispatch(checkedObjectsReset()) + var blob = new Blob([this.response], { + type: 'application/zip' + }) + var blobUrl = window.URL.createObjectURL(blob); + window.location = blobUrl + } + }; + xhr.send(JSON.stringify(req)); + } +} + export const uploadFile = (file, xhr) => { return (dispatch, getState) => { const {currentBucket, currentPath} = getState() @@ -563,3 +587,24 @@ export const setPolicies = (policies) => { policies } } + +export const checkedObjectsAdd = (objectName) => { + return { + type: CHECKED_OBJECTS_ADD, + objectName + } +} + +export const checkedObjectsRemove = (objectName) => { + return { + type: CHECKED_OBJECTS_REMOVE, + objectName + } +} + +export const checkedObjectsReset = (objectName) => { + return { + type: CHECKED_OBJECTS_RESET, + objectName + } +} diff --git a/browser/app/js/components/Browse.js b/browser/app/js/components/Browse.js index 6458ad22b..85550ccb6 100644 --- a/browser/app/js/components/Browse.js +++ b/browser/app/js/components/Browse.js @@ -1,5 +1,5 @@ /* - * Minio Browser (C) 2016 Minio, Inc. + * Minio Cloud Storage (C) 2016 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,7 +27,6 @@ import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger' import Tooltip from 'react-bootstrap/lib/Tooltip' import Dropdown from 'react-bootstrap/lib/Dropdown' import MenuItem from 'react-bootstrap/lib/MenuItem' - import InputGroup from '../components/InputGroup' import Dropzone from '../components/Dropzone' import ObjectsList from '../components/ObjectsList' @@ -227,14 +226,24 @@ export default class Browse extends React.Component { } removeObject() { - const {web, dispatch, currentPath, currentBucket, deleteConfirmation} = this.props + const {web, dispatch, currentPath, currentBucket, deleteConfirmation, checkedObjects} = this.props + let objects = checkedObjects.length > 0 ? checkedObjects : [deleteConfirmation.object] + web.RemoveObject({ - bucketName: currentBucket, - objectName: deleteConfirmation.object + bucketname: currentBucket, + objects: objects }) .then(() => { this.hideDeleteConfirmation() - dispatch(actions.removeObject(deleteConfirmation.object)) + if (checkedObjects.length > 0) { + for (let i = 0; i < checkedObjects.length; i++) { + dispatch(actions.removeObject(checkedObjects[i].replace(currentPath, ''))) + } + dispatch(actions.checkedObjectsReset()) + } else { + let delObject = deleteConfirmation.object.replace(currentPath, '') + dispatch(actions.removeObject(delObject)) + } }) .catch(e => dispatch(actions.showAlert({ type: 'danger', @@ -262,7 +271,8 @@ export default class Browse extends React.Component { shareObject(e, object) { e.preventDefault() const {dispatch} = this.props - dispatch(actions.shareObject(object)) + // let expiry = 5 * 24 * 60 * 60 // 5 days expiry by default + dispatch(actions.shareObject(object, 5, 0, 0)) } hideShareObjectModal() { @@ -354,18 +364,46 @@ export default class Browse extends React.Component { this.refs.copyTextInput.select() } - handleExpireValue(targetInput, inc) { + handleExpireValue(targetInput, inc, object) { inc === -1 ? this.refs[targetInput].stepDown(1) : this.refs[targetInput].stepUp(1) if (this.refs.expireDays.value == 7) { this.refs.expireHours.value = 0 this.refs.expireMins.value = 0 } + if (this.refs.expireDays.value + this.refs.expireHours.value + this.refs.expireMins.value == 0) { + this.refs.expireDays.value = 7 + } + const {dispatch} = this.props + dispatch(actions.shareObject(object, this.refs.expireDays.value, this.refs.expireHours.value, this.refs.expireMins.value)) + } + + checkObject(e, objectName) { + const {dispatch} = this.props + e.target.checked ? dispatch(actions.checkedObjectsAdd(objectName)) : dispatch(actions.checkedObjectsRemove(objectName)) + } + + downloadSelected() { + const {dispatch} = this.props + let req = { + bucketName: this.props.currentBucket, + objects: this.props.checkedObjects, + prefix: this.props.currentPath + } + let requestUrl = location.origin + "/minio/zip?token=" + localStorage.token + + this.xhr = new XMLHttpRequest() + dispatch(actions.downloadSelected(requestUrl, req, this.xhr)) + } + + clearSelected() { + const {dispatch} = this.props + dispatch(actions.checkedObjectsReset()) } render() { const {total, free} = this.props.storageInfo - const {showMakeBucketModal, alert, sortNameOrder, sortSizeOrder, sortDateOrder, showAbout, showBucketPolicy} = this.props + const {showMakeBucketModal, alert, sortNameOrder, sortSizeOrder, sortDateOrder, showAbout, showBucketPolicy, checkedObjects} = this.props const {version, memory, platform, runtime} = this.props.serverInfo const {sidebarStatus} = this.props const {showSettings} = this.props @@ -435,7 +473,6 @@ export default class Browse extends React.Component { - } let createButton = '' @@ -490,6 +527,14 @@ export default class Browse extends React.Component { clickOutside={ this.hideSidebar.bind(this) } showPolicy={ this.showBucketPolicy.bind(this) } />
+
0 + })) }> + { checkedObjects.length } Objects selected + + + +
{ alertBox }
@@ -515,7 +560,8 @@ export default class Browse extends React.Component {
-
+
+
Name
-
+
Size
-
+
Last Modified
-
+
@@ -553,9 +599,11 @@ export default class Browse extends React.Component { + shareObject={ this.shareObject.bind(this) } + checkObject={ this.checkObject.bind(this) } + checkedObjectsArray={ checkedObjects } /> -
+
Loading...
@@ -673,7 +721,7 @@ export default class Browse extends React.Component {
- +
Days
@@ -682,12 +730,12 @@ export default class Browse extends React.Component { type="number" min={ 0 } max={ 7 } - defaultValue={ 0 } /> + defaultValue={ 5 } />
- +
- +
Hours
@@ -695,30 +743,30 @@ export default class Browse extends React.Component {
- +
- +
Minutes
+ min={ 0 } + max={ 59 } + defaultValue={ 0 } />
- +
-
+
- + diff --git a/browser/app/js/components/BrowserDropdown.js b/browser/app/js/components/BrowserDropdown.js index 171e6f3e7..aa4dc9ac9 100644 --- a/browser/app/js/components/BrowserDropdown.js +++ b/browser/app/js/components/BrowserDropdown.js @@ -1,5 +1,5 @@ /* - * Minio Browser (C) 2016, 2017 Minio, Inc. + * Minio Cloud Storage (C) 2016, 2017 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,7 +27,7 @@ let BrowserDropdown = ({fullScreenFunc, aboutFunc, settingsFunc, logoutFunc}) =>
  • - Github + Github
  • Fullscreen diff --git a/browser/app/js/components/BrowserUpdate.js b/browser/app/js/components/BrowserUpdate.js index be4c7af3a..5eaae8233 100644 --- a/browser/app/js/components/BrowserUpdate.js +++ b/browser/app/js/components/BrowserUpdate.js @@ -1,5 +1,5 @@ /* - * Minio Browser (C) 2016 Minio, Inc. + * Minio Cloud Storage (C) 2016 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/browser/app/js/components/ConfirmModal.js b/browser/app/js/components/ConfirmModal.js index fd98fa313..3677c3bbd 100644 --- a/browser/app/js/components/ConfirmModal.js +++ b/browser/app/js/components/ConfirmModal.js @@ -1,5 +1,5 @@ /* - * Minio Browser (C) 2016 Minio, Inc. + * Minio Cloud Storage (C) 2016 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/browser/app/js/components/Dropzone.js b/browser/app/js/components/Dropzone.js index 0ddab2661..32a139d7f 100644 --- a/browser/app/js/components/Dropzone.js +++ b/browser/app/js/components/Dropzone.js @@ -1,5 +1,5 @@ /* - * Minio Browser (C) 2016 Minio, Inc. + * Minio Cloud Storage (C) 2016 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,11 +39,12 @@ export default class Dropzone extends React.Component { // won't handle child elements correctly. const style = { height: '100%', - borderWidth: '2px', + borderWidth: '0', borderStyle: 'dashed', borderColor: '#fff' } const activeStyle = { + borderWidth: '2px', borderColor: '#777' } const rejectStyle = { diff --git a/browser/app/js/components/InputGroup.js b/browser/app/js/components/InputGroup.js index c2b0e2ab2..9aee63f4c 100644 --- a/browser/app/js/components/InputGroup.js +++ b/browser/app/js/components/InputGroup.js @@ -1,5 +1,5 @@ /* - * Minio Browser (C) 2016 Minio, Inc. + * Minio Cloud Storage (C) 2016 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/browser/app/js/components/Login.js b/browser/app/js/components/Login.js index b5db1a872..5293e73d6 100644 --- a/browser/app/js/components/Login.js +++ b/browser/app/js/components/Login.js @@ -1,5 +1,5 @@ /* - * Minio Browser (C) 2016 Minio, Inc. + * Minio Cloud Storage (C) 2016 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/browser/app/js/components/ObjectsList.js b/browser/app/js/components/ObjectsList.js index 623594218..acba46238 100644 --- a/browser/app/js/components/ObjectsList.js +++ b/browser/app/js/components/ObjectsList.js @@ -1,5 +1,5 @@ /* - * Minio Browser (C) 2016 Minio, Inc. + * Minio Cloud Storage (C) 2016 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,8 +20,7 @@ import humanize from 'humanize' import connect from 'react-redux/lib/components/connect' import Dropdown from 'react-bootstrap/lib/Dropdown' - -let ObjectsList = ({objects, currentPath, selectPrefix, dataType, showDeleteConfirmation, shareObject, loadPath}) => { +let ObjectsList = ({objects, currentPath, selectPrefix, dataType, showDeleteConfirmation, shareObject, loadPath, checkObject, checkedObjectsArray}) => { const list = objects.map((object, i) => { let size = object.name.endsWith('/') ? '-' : humanize.filesize(object.size) let lastModified = object.name.endsWith('/') ? '-' : Moment(object.lastModified).format('lll') @@ -30,29 +29,51 @@ let ObjectsList = ({objects, currentPath, selectPrefix, dataType, showDeleteConf let deleteButton = '' if (web.LoggedIn()) deleteButton = showDeleteConfirmation(e, `${currentPath}${object.name}`) }> - if (!object.name.endsWith('/')) { - actionButtons = - - - shareObject(e, `${currentPath}${object.name}`) }> - { deleteButton } - - + + if (!checkedObjectsArray.length > 0) { + if (!object.name.endsWith('/')) { + actionButtons = + + + shareObject(e, `${currentPath}${object.name}`) }> + { deleteButton } + + + } } + + let activeClass = '' + let isChecked = '' + + if (checkedObjectsArray.indexOf(object.name) > -1) { + activeClass = ' fesl-row-selected' + isChecked = true + } + return ( -
    -
    +
    +
    +
    + checkObject(e, object.name) } /> + + +
    +
    + -
    +
    { size }
    -
    +
    { lastModified }
    -
    +
    { actionButtons }
    @@ -72,4 +93,4 @@ export default connect(state => { currentPath: state.currentPath, loadPath: state.loadPath } -})(ObjectsList) +})(ObjectsList) \ No newline at end of file diff --git a/browser/app/js/components/Path.js b/browser/app/js/components/Path.js index 901f094aa..55484da70 100644 --- a/browser/app/js/components/Path.js +++ b/browser/app/js/components/Path.js @@ -1,5 +1,5 @@ /* - * Minio Browser (C) 2016 Minio, Inc. + * Minio Cloud Storage (C) 2016 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/browser/app/js/components/Policy.js b/browser/app/js/components/Policy.js index 65930ad64..cdf95eedf 100644 --- a/browser/app/js/components/Policy.js +++ b/browser/app/js/components/Policy.js @@ -77,4 +77,4 @@ class Policy extends Component { } } -export default connect(state => state)(Policy) +export default connect(state => state)(Policy) \ No newline at end of file diff --git a/browser/app/js/components/PolicyInput.js b/browser/app/js/components/PolicyInput.js index 75809df96..9353bde41 100644 --- a/browser/app/js/components/PolicyInput.js +++ b/browser/app/js/components/PolicyInput.js @@ -80,4 +80,4 @@ class PolicyInput extends Component { } } -export default connect(state => state)(PolicyInput) +export default connect(state => state)(PolicyInput) \ No newline at end of file diff --git a/browser/app/js/components/SettingsModal.js b/browser/app/js/components/SettingsModal.js index 51bd4333b..9d3263a46 100644 --- a/browser/app/js/components/SettingsModal.js +++ b/browser/app/js/components/SettingsModal.js @@ -1,5 +1,5 @@ /* - * Minio Browser (C) 2016 Minio, Inc. + * Minio Cloud Storage (C) 2016 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/browser/app/js/components/SideBar.js b/browser/app/js/components/SideBar.js index d92bfbceb..b06571e2b 100644 --- a/browser/app/js/components/SideBar.js +++ b/browser/app/js/components/SideBar.js @@ -1,5 +1,5 @@ /* - * Minio Browser (C) 2016 Minio, Inc. + * Minio Cloud Storage (C) 2016 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/browser/app/js/components/UploadModal.js b/browser/app/js/components/UploadModal.js index 6658ab225..f68b32ee8 100644 --- a/browser/app/js/components/UploadModal.js +++ b/browser/app/js/components/UploadModal.js @@ -1,5 +1,5 @@ /* - * Minio Browser (C) 2016 Minio, Inc. + * Minio Cloud Storage (C) 2016 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/browser/app/js/components/__tests__/Login-test.js b/browser/app/js/components/__tests__/Login-test.js index 1397fb637..ac3b3dd6d 100644 --- a/browser/app/js/components/__tests__/Login-test.js +++ b/browser/app/js/components/__tests__/Login-test.js @@ -1,5 +1,5 @@ /* - * Minio Browser (C) 2016 Minio, Inc. + * Minio Cloud Storage (C) 2016 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/browser/app/js/constants.js b/browser/app/js/constants.js index 35c20f418..8f2dc34c2 100644 --- a/browser/app/js/constants.js +++ b/browser/app/js/constants.js @@ -1,5 +1,5 @@ /* - * Minio Browser (C) 2016 Minio, Inc. + * Minio Cloud Storage (C) 2016 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/browser/app/js/jsonrpc.js b/browser/app/js/jsonrpc.js index 99e1d1102..ece92ee1b 100644 --- a/browser/app/js/jsonrpc.js +++ b/browser/app/js/jsonrpc.js @@ -1,5 +1,5 @@ /* - * Minio Browser (C) 2016 Minio, Inc. + * Minio Cloud Storage (C) 2016 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/browser/app/js/mime.js b/browser/app/js/mime.js index 9c8ed40fa..aed2e753d 100644 --- a/browser/app/js/mime.js +++ b/browser/app/js/mime.js @@ -1,5 +1,5 @@ /* - * Minio Browser (C) 2016 Minio, Inc. + * Minio Cloud Storage (C) 2016 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/browser/app/js/reducers.js b/browser/app/js/reducers.js index ca312fbe7..c8c21db9b 100644 --- a/browser/app/js/reducers.js +++ b/browser/app/js/reducers.js @@ -1,5 +1,5 @@ /* - * Minio Browser (C) 2016 Minio, Inc. + * Minio Cloud Storage (C) 2016 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -54,9 +54,10 @@ export default (state = { shareObject: { show: false, url: '', - expiry: 604800 + object: '' }, - prefixWritable: false + prefixWritable: false, + checkedObjects: [] }, action) => { let newState = Object.assign({}, state) switch (action.type) { @@ -82,7 +83,7 @@ export default (state = { newState.marker = "" newState.istruncated = action.istruncated } else { - newState.objects = [...newState.objects, ...action.objects] + newState.objects = [...action.objects] newState.marker = action.marker newState.istruncated = action.istruncated } @@ -185,6 +186,19 @@ export default (state = { if (idx == -1) break newState.objects = [...newState.objects.slice(0, idx), ...newState.objects.slice(idx + 1)] break + + case actions.CHECKED_OBJECTS_ADD: + newState.checkedObjects = [...newState.checkedObjects, action.objectName] + break + case actions.CHECKED_OBJECTS_REMOVE: + let index = newState.checkedObjects.indexOf(action.objectName) + if (index == -1) break + newState.checkedObjects = [...newState.checkedObjects.slice(0, index), ...newState.checkedObjects.slice(index + 1)] + break + case actions.CHECKED_OBJECTS_RESET: + newState.checkedObjects = [] + break } + return newState } diff --git a/browser/app/js/utils.js b/browser/app/js/utils.js index 3aee71a1b..91afff20c 100644 --- a/browser/app/js/utils.js +++ b/browser/app/js/utils.js @@ -1,5 +1,5 @@ /* - * Minio Browser (C) 2016 Minio, Inc. + * Minio Cloud Storage (C) 2016 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/browser/app/js/web.js b/browser/app/js/web.js index a4c241137..96b37ba05 100644 --- a/browser/app/js/web.js +++ b/browser/app/js/web.js @@ -1,5 +1,5 @@ /* - * Minio Browser (C) 2016 Minio, Inc. + * Minio Cloud Storage (C) 2016 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/browser/app/less/inc/buttons.less b/browser/app/less/inc/buttons.less index 28131641a..6c04a95da 100644 --- a/browser/app/less/inc/buttons.less +++ b/browser/app/less/inc/buttons.less @@ -35,6 +35,10 @@ width: 100%; } +.btn-white { + .btn-variant(#fff, darken(@text-color, 20%)); +} + .btn-link { .btn-variant(#eee, #545454); } diff --git a/browser/app/less/inc/header.less b/browser/app/less/inc/header.less index e95f05d66..50c619bc1 100644 --- a/browser/app/less/inc/header.less +++ b/browser/app/less/inc/header.less @@ -2,14 +2,13 @@ Header ----------------------------*/ .fe-header { - padding: 45px 55px 20px; - - @media(min-width: @screen-md-min) { + @media(min-width: (@screen-sm-min - 100)) { position: relative; + padding: 40px 40px 20px 45px; } @media(max-width: (@screen-xs-max - 100)) { - padding: 25px 25px 20px; + padding: 20px; } h2 { @@ -239,4 +238,3 @@ } - diff --git a/browser/app/less/inc/list.less b/browser/app/less/inc/list.less index d3ee399b1..5cb0a9ffb 100644 --- a/browser/app/less/inc/list.less +++ b/browser/app/less/inc/list.less @@ -2,17 +2,19 @@ Row ----------------------------*/ .fesl-row { - padding-right: 40px; - padding-top: 5px; - padding-bottom: 5px; position: relative; @media (min-width: (@screen-sm-min - 100px)) { + padding: 5px 20px 5px 40px; display: flex; flex-flow: row nowrap; justify-content: space-between; } + @media(max-width: (@screen-xs-max - 100px)) { + padding: 5px 20px; + } + .clearfix(); } @@ -20,7 +22,7 @@ header.fesl-row { @media (min-width:(@screen-sm-min - 100px)) { margin-bottom: 20px; border-bottom: 1px solid lighten(@text-muted-color, 20%); - padding-left: 40px; + padding-left: 30px; .fesl-item, .fesli-sort { @@ -42,7 +44,7 @@ header.fesl-row { font-size: 14px; } - &:hover:not(.fi-actions) { + &:hover:not(.fesl-item-actions) { background: lighten(@text-muted-color, 22%); color: @dark-gray; @@ -58,54 +60,42 @@ header.fesl-row { } } +.list-type(@background, @icon) { + .fis-icon { + background-color: @background; + + &:before { + content: @icon; + } + } +} + div.fesl-row { - padding-left: 85px; border-bottom: 1px solid transparent; cursor: default; + .transition(background-color); + .transition-duration(500ms); @media (max-width: (@screen-xs-max - 100px)) { - padding-left: 70px; - padding-right: 45px; + padding: 5px 20px; } - &:nth-child(even) { - background-color: #fafafa; - } - - &:hover { - background-color: #fbf7dc; - } - - &[data-type]:before { - font-family: @font-family-icon; - width: 35px; - height: 35px; - text-align: center; - line-height: 35px; - position: absolute; - border-radius: 50%; - font-size: 16px; - left: 50px; - top: 9px; - color: @white; - - @media (max-width: (@screen-xs-max - 100px)) { - left: 20px; + &:not(.fesl-row-selected) { + &:nth-child(even) { + background-color: @list-row-even-bg; } } - &[data-type="folder"] { - @media (max-width: (@screen-xs-max - 100px)) { - .fesl-item { - &.fi-name { - padding-top: 10px; - padding-bottom: 7px; - } + &:hover { + .fis-icon { + &:before { + .opacity(0) + } + } - &.fi-size, - &.fi-modified { - display: none; - } + .fis-helper { + &:before { + .opacity(1); } } } @@ -113,54 +103,18 @@ div.fesl-row { /*-------------------------- Icons ----------------------------*/ - &[data-type=folder]:before { - content: '\f114'; - background-color: #a1d6dd; - } - &[data-type=pdf]:before { - content: "\f1c1"; - background-color: #fa7775; - } - &[data-type=zip]:before { - content: "\f1c6"; - background-color: #427089; - } - &[data-type=audio]:before { - content: "\f1c7"; - background-color: #009688 - } - &[data-type=code]:before { - content: "\f1c9"; - background-color: #997867; - } - &[data-type=excel]:before { - content: "\f1c3"; - background-color: #64c866; - } - &[data-type=image]:before { - content: "\f1c5"; - background-color: #f06292; - } - &[data-type=video]:before { - content: "\f1c8"; - background-color: #f8c363; - } - &[data-type=other]:before { - content: "\f016"; - background-color: #afafaf; - } - &[data-type=text]:before { - content: "\f0f6"; - background-color: #8a8a8a; - } - &[data-type=doc]:before { - content: "\f1c2"; - background-color: #2196f5; - } - &[data-type=presentation]:before { - content: "\f1c4"; - background-color: #896ea6; - } + &[data-type=folder] { .list-type(#a1d6dd, '\f114'); } + &[data-type=pdf] {.list-type(#fa7775, '\f1c1'); } + &[data-type=zip] { .list-type(#427089, '\f1c6'); } + &[data-type=audio] { .list-type(#009688, '\f1c7'); } + &[data-type=code] { .list-type(#997867, "\f1c9"); } + &[data-type=excel] { .list-type(#f1c3, '\f1c3'); } + &[data-type=image] { .list-type(#f06292, '\f1c5'); } + &[data-type=video] { .list-type(#f8c363, '\f1c8'); } + &[data-type=other] { .list-type(#afafaf, '\f016'); } + &[data-type=text] { .list-type(#8a8a8a, '\f0f6'); } + &[data-type=doc] { .list-type(#2196f5, '\f1c2'); } + &[data-type=presentation] { .list-type(#896ea6, '\f1c4'); } &.fesl-loading{ &:before { @@ -180,6 +134,113 @@ div.fesl-row { } } +.fesl-row-selected { + background-color: @list-row-selected-bg; + + &, .fesl-item a { + color: darken(@text-color, 10%); + } +} + +.fi-select { + float: left; + position: relative; + width: 35px; + height: 35px; + margin: 3px 0; + + @media(max-width: (@screen-xs-max - 100px)) { + margin-right: 15px; + } + + input { + position: absolute; + left: 0; + top: 0; + width: 35px; + height: 35px; + z-index: 20; + opacity: 0; + cursor: pointer; + + &:checked { + & ~ .fis-icon { + background-color: #32393F; + + &:before { + opacity: 0; + } + } + + & ~ .fis-helper { + &:before { + .scale(0); + } + + &:after { + .scale(1); + } + } + } + } +} + +.fis-icon { + display: inline-block; + vertical-align: top; + border-radius: 50%; + width: 35px; + height: 35px; + .transition(background-color); + .transition-duration(250ms); + + &:before { + width: 100%; + height: 100%; + text-align: center; + position: absolute; + border-radius: 50%; + font-family: @font-family-icon; + line-height: 35px; + font-size: 16px; + color: @white; + .transition(all); + .transition-duration(300ms); + font-style: normal; + } +} + +.fis-helper { + &:before, + &:after { + position: absolute; + .transition(all); + .transition-duration(250ms); + } + + &:before { + content: ''; + width: 15px; + height: 15px; + border: 2px solid @white; + z-index: 10; + border-radius: 2px; + top: 10px; + left: 10px; + opacity: 0; + } + + &:after { + font-family: @font-family-icon; + content: '\f00c'; + top: 8px; + left: 9px; + color: @white; + font-size: 14px; + .scale(0); + } +} + /*-------------------------- Files and Folders @@ -192,26 +253,26 @@ div.fesl-row { } @media(min-width: (@screen-sm-min - 100px)) { - &:not(.fi-actions) { + &:not(.fesl-item-actions):not(.fesl-item-icon) { text-overflow: ellipsis; padding: 10px 15px; white-space: nowrap; overflow: hidden; } - &.fi-name { + &.fesl-item-name { flex: 3; } - &.fi-size { + &.fesl-item-size { width: 140px; } - &.fi-modified { + &.fesl-item-modified { width: 190px; } - &.fi-actions { + &.fesl-item-actions { width: 40px; } } @@ -219,29 +280,29 @@ div.fesl-row { @media(max-width: (@screen-xs-max - 100px)) { padding: 0; - &.fi-name { + &.fesl-item-name { width: 100%; margin-bottom: 3px; } - &.fi-size, - &.fi-modified { + &.fesl-item-size, + &.fesl-item-modified { font-size: 12px; color: #B5B5B5; float: left; } - &.fi-modified { + &.fesl-item-modified { max-width: 72px; white-space: nowrap; overflow: hidden; } - &.fi-size { + &.fesl-item-size { margin-right: 10px; } - &.fi-actions { + &.fesl-item-actions { position: absolute; top: 5px; right: 10px; @@ -266,7 +327,7 @@ div.fesl-row { } } -.fi-actions { +.fesl-item-actions { .dropdown-menu { background-color: transparent; box-shadow: none; @@ -324,6 +385,79 @@ div.fesl-row { } } +.list-actions { + position: fixed; + .translate3d(0, -100%, 0); + .opacity(0); + .transition(all); + .transition-duration(200ms); + padding: 20px 70px 20px 25px; + top: 0; + left: 0; + width: 100%; + background-color: @brand-primary; + z-index: 20; + box-shadow: 0 0 10px rgba(0,0,0,0.3); + text-align: center; + + &.list-actions-toggled { + .translate3d(0, 0, 0); + .opacity(1); + } +} + +.la-close { + position: absolute; + right: 20px; + top: 0; + color: #fff; + width: 30px; + height: 30px; + border-radius: 50%; + text-align: center; + line-height: 30px !important; + background: rgba(255, 255, 255, 0.1); + font-weight: normal; + bottom: 0; + margin: auto; + cursor: pointer; + + &:hover { + background-color: rgba(255, 255, 255, 0.2); + } +} + +.la-label { + color: @white; + float: left; + padding: 4px 0; + + .fa { + font-size: 22px; + vertical-align: top; + margin-right: 10px; + margin-top: -1px; + } +} + +.la-actions { + button { + background-color: transparent; + border: 2px solid rgba(255,255,255,0.9); + color: @white; + border-radius: 2px; + padding: 5px 10px; + font-size: 13px; + .transition(all); + .transition-duration(300ms); + margin-left: 10px; + + &:hover { + background-color: @white; + color: @brand-primary; + } + } +} @-webkit-keyframes fiad-action-anim { from { diff --git a/browser/app/less/inc/misc.less b/browser/app/less/inc/misc.less index dba1b43b5..8359d426d 100644 --- a/browser/app/less/inc/misc.less +++ b/browser/app/less/inc/misc.less @@ -99,4 +99,18 @@ content: '7 days'; right: 0; } +} + + +.modal-aheader { + height: 100px; + + &:before { + height: 0 !important; + } + + .modal-dialog { + margin: 0; + vertical-align: top; + } } \ No newline at end of file diff --git a/browser/app/less/inc/mixin.less b/browser/app/less/inc/mixin.less index 528f2f26b..27a764817 100644 --- a/browser/app/less/inc/mixin.less +++ b/browser/app/less/inc/mixin.less @@ -49,4 +49,4 @@ z-index: 1; -webkit-animation: zoomIn 250ms, spin 700ms 250ms infinite linear; animation: zoomIn 250ms, spin 700ms 250ms infinite linear; -} \ No newline at end of file +} diff --git a/browser/app/less/inc/sidebar.less b/browser/app/less/inc/sidebar.less index c975472eb..5000cc03c 100644 --- a/browser/app/less/inc/sidebar.less +++ b/browser/app/less/inc/sidebar.less @@ -7,7 +7,7 @@ position: fixed; height: 100%; overflow: hidden; - padding: 35px; + padding: 25px; @media(min-width: @screen-md-min) { .translate3d(0, 0, 0); @@ -63,15 +63,15 @@ height: ~"calc(100vh - 260px)"; overflow: auto; padding: 0; - margin: 0 -35px; + margin: 0 -25px; & li { position: relative; & > a { display: block; - padding: 10px 40px 12px 65px; - .text-overflow(); + padding: 10px 45px 12px 55px; + word-wrap: break-word; &:before { font-family: FontAwesome; @@ -79,7 +79,7 @@ font-size: 17px; position: absolute; top: 10px; - left: 35px; + left: 25px; .opacity(0.8); } @@ -95,7 +95,7 @@ } &.active { - background-color: rgba(0, 0, 0, 0.2); + background-color: #282e32; & > a { color: @white; @@ -139,10 +139,10 @@ position: absolute; top: 0; right: 0; - width: 40px; + width: 35px; height: 100%; cursor: pointer; - background: url(../../img/more-h-light.svg) no-repeat left; + background: url(../../img/more-h-light.svg) no-repeat top 20px left; } /* Scrollbar */ diff --git a/browser/app/less/inc/variables.less b/browser/app/less/inc/variables.less index de6589994..a89008007 100644 --- a/browser/app/less/inc/variables.less +++ b/browser/app/less/inc/variables.less @@ -13,7 +13,7 @@ /*-------------------------- File Explorer ----------------------------*/ -@fe-sidebar-width : 300px; +@fe-sidebar-width : 320px; @text-muted-color : #BDBDBD; @text-strong-color : #333; @@ -81,7 +81,7 @@ /*------------------------- Colors --------------------------*/ -@brand-primary: #2196F3; +@brand-primary: #2298f7; @brand-success: #4CAF50; @brand-info: #00BCD4; @brand-warning: #FF9800; @@ -91,4 +91,11 @@ /*------------------------- Form --------------------------*/ -@input-border: #eee; \ No newline at end of file +@input-border: #eee; + + +/*------------------------- + List +--------------------------*/ +@list-row-selected-bg: #fbf2bf; +@list-row-even-bg: #fafafa; \ No newline at end of file diff --git a/browser/build.js b/browser/build.js index f612b7d02..ff12e0238 100644 --- a/browser/build.js +++ b/browser/build.js @@ -1,5 +1,5 @@ /* - * Minio Browser (C) 2016 Minio, Inc. + * Minio Cloud Storage (C) 2016 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -70,9 +70,9 @@ async.waterfall([ commitId = stdout.replace('\n', '') if (commitId.length !== 40) throw new Error('commitId invalid : ' + commitId) assetsFileName = 'ui-assets.go'; - var cmd = 'go-bindata-assetfs -pkg miniobrowser -nocompress=true production/...' + var cmd = 'go-bindata-assetfs -pkg browser -nocompress=true production/...' if (!isProduction) { - cmd = 'go-bindata-assetfs -pkg miniobrowser -nocompress=true dev/...' + cmd = 'go-bindata-assetfs -pkg browser -nocompress=true dev/...' } console.log('Running', cmd) exec(cmd, cb) diff --git a/browser/package.json b/browser/package.json index 583d87621..227efdf5d 100644 --- a/browser/package.json +++ b/browser/package.json @@ -1,5 +1,5 @@ { - "name": "minio-browser", + "name": "browser", "version": "0.0.1", "description": "Minio Browser", "scripts": { @@ -11,14 +11,14 @@ }, "repository": { "type": "git", - "url": "https://github.com/minio/miniobrowser" + "url": "https://github.com/minio/minio" }, "author": "Minio Inc", "license": "Apache-2.0", "bugs": { - "url": "https://github.com/minio/miniobrowser/issues" + "url": "https://github.com/minio/minio/issues" }, - "homepage": "https://github.com/minio/miniobrowser", + "homepage": "https://github.com/minio/minio", "devDependencies": { "async": "^1.5.2", "babel-cli": "^6.14.0", @@ -26,6 +26,7 @@ "babel-loader": "^6.2.5", "babel-plugin-syntax-object-rest-spread": "^6.13.0", "babel-plugin-transform-object-rest-spread": "^6.8.0", + "babel-polyfill": "^6.23.0", "babel-preset-es2015": "^6.14.0", "babel-preset-react": "^6.11.1", "babel-register": "^6.14.0", diff --git a/browser/ui-assets.go b/browser/ui-assets.go index 773e9288b..e4b435709 100644 --- a/browser/ui-assets.go +++ b/browser/ui-assets.go @@ -4,13 +4,13 @@ // production/favicon.ico // production/firefox.png // production/index.html -// production/index_bundle-2017-02-15T22-41-23Z.js +// production/index_bundle-2017-03-15T17-49-51Z.js // production/loader.css // production/logo.svg // production/safari.png // DO NOT EDIT! -package miniobrowser +package browser import ( "fmt" @@ -65,7 +65,7 @@ func productionChromePng() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "production/chrome.png", size: 3726, mode: os.FileMode(436), modTime: time.Unix(1487198497, 0)} + info := bindataFileInfo{name: "production/chrome.png", size: 3726, mode: os.FileMode(436), modTime: time.Unix(1489600214, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -82,7 +82,7 @@ func productionFaviconIco() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "production/favicon.ico", size: 1340, mode: os.FileMode(436), modTime: time.Unix(1487198497, 0)} + info := bindataFileInfo{name: "production/favicon.ico", size: 1340, mode: os.FileMode(436), modTime: time.Unix(1489600214, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -99,7 +99,7 @@ func productionFirefoxPng() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "production/firefox.png", size: 4795, mode: os.FileMode(436), modTime: time.Unix(1487198497, 0)} + info := bindataFileInfo{name: "production/firefox.png", size: 4795, mode: os.FileMode(436), modTime: time.Unix(1489600214, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -156,8 +156,8 @@ var _productionIndexHTML = []byte(`
    - - + + `) @@ -172,21 +172,21 @@ func productionIndexHTML() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "production/index.html", size: 1996, mode: os.FileMode(436), modTime: time.Unix(1487198497, 0)} + info := bindataFileInfo{name: "production/index.html", size: 1996, mode: os.FileMode(436), modTime: time.Unix(1489600214, 0)} a := &asset{bytes: bytes, info: info} return a, nil } -var _productionIndex_bundle20170215t224123zJs = []byte(`!function(A){function M(I){if(t[I])return t[I].exports;var g=t[I]={exports:{},id:I,loaded:!1};return A[I].call(g.exports,g,g.exports,M),g.loaded=!0,g.exports}var t={};return M.m=A,M.c=t,M.p="",M(0)}([function(A,M,t){A.exports=t(240)},function(A,M,t){"use strict";A.exports=t(440)},function(A,M,t){"use strict";function I(A,M,t,I,g,e,i,T){if(!A){var E;if(void 0===M)E=new Error("Minified exception occurred; use the non-minified dev environment for the full error message and additional helpful warnings.");else{var N=[t,I,g,e,i,T],o=0;E=new Error(M.replace(/%s/g,function(){return N[o++]})),E.name="Invariant Violation"}throw E.framesToPop=1,E}}A.exports=I},function(A,M,t){"use strict";var I=t(20),g=I;A.exports=g},function(A,M){"use strict";function t(A,M){if(null==A)throw new TypeError("Object.assign target cannot be null or undefined");for(var t=Object(A),I=Object.prototype.hasOwnProperty,g=1;g2?t-2:0),g=2;g1){for(var c=Array(C),D=0;D1){for(var a=Array(D),B=0;B3&&void 0!==arguments[3]?arguments[3]:{},N=Boolean(A),C=A||w,D=void 0;D="function"==typeof M?M:M?(0,Q.default)(M):L;var B=t||l,r=I.pure,s=void 0===r||r,x=I.withRef,y=void 0!==x&&x,h=s&&B!==l,S=d++;return function(A){function M(A,M,t){var I=B(A,M,t);return I}var t="Connect("+T(A)+")",I=function(I){function T(A,M){g(this,T);var i=e(this,I.call(this,A,M));i.version=S,i.store=A.store||M.store,(0,u.default)(i.store,'Could not find "store" in either the context or '+('props of "'+t+'". ')+"Either wrap the root component in a , "+('or explicitly pass "store" as a prop to "'+t+'".'));var E=i.store.getState();return i.state={storeState:E},i.clearCache(),i}return i(T,I),T.prototype.shouldComponentUpdate=function(){return!s||this.haveOwnPropsChanged||this.hasStoreStateChanged},T.prototype.computeStateProps=function(A,M){if(!this.finalMapStateToProps)return this.configureFinalMapState(A,M);var t=A.getState(),I=this.doStatePropsDependOnOwnProps?this.finalMapStateToProps(t,M):this.finalMapStateToProps(t);return I},T.prototype.configureFinalMapState=function(A,M){var t=C(A.getState(),M),I="function"==typeof t;return this.finalMapStateToProps=I?t:C,this.doStatePropsDependOnOwnProps=1!==this.finalMapStateToProps.length,I?this.computeStateProps(A,M):t},T.prototype.computeDispatchProps=function(A,M){if(!this.finalMapDispatchToProps)return this.configureFinalMapDispatch(A,M);var t=A.dispatch,I=this.doDispatchPropsDependOnOwnProps?this.finalMapDispatchToProps(t,M):this.finalMapDispatchToProps(t);return I},T.prototype.configureFinalMapDispatch=function(A,M){var t=D(A.dispatch,M),I="function"==typeof t;return this.finalMapDispatchToProps=I?t:D,this.doDispatchPropsDependOnOwnProps=1!==this.finalMapDispatchToProps.length,I?this.computeDispatchProps(A,M):t},T.prototype.updateStatePropsIfNeeded=function(){var A=this.computeStateProps(this.store,this.props);return(!this.stateProps||!(0,a.default)(A,this.stateProps))&&(this.stateProps=A,!0)},T.prototype.updateDispatchPropsIfNeeded=function(){var A=this.computeDispatchProps(this.store,this.props);return(!this.dispatchProps||!(0,a.default)(A,this.dispatchProps))&&(this.dispatchProps=A,!0)},T.prototype.updateMergedPropsIfNeeded=function(){var A=M(this.stateProps,this.dispatchProps,this.props);return!(this.mergedProps&&h&&(0,a.default)(A,this.mergedProps))&&(this.mergedProps=A,!0)},T.prototype.isSubscribed=function(){return"function"==typeof this.unsubscribe},T.prototype.trySubscribe=function(){N&&!this.unsubscribe&&(this.unsubscribe=this.store.subscribe(this.handleChange.bind(this)),this.handleChange())},T.prototype.tryUnsubscribe=function(){this.unsubscribe&&(this.unsubscribe(),this.unsubscribe=null)},T.prototype.componentDidMount=function(){this.trySubscribe()},T.prototype.componentWillReceiveProps=function(A){s&&(0,a.default)(A,this.props)||(this.haveOwnPropsChanged=!0)},T.prototype.componentWillUnmount=function(){this.tryUnsubscribe(),this.clearCache()},T.prototype.clearCache=function(){this.dispatchProps=null,this.stateProps=null,this.mergedProps=null,this.haveOwnPropsChanged=!0,this.hasStoreStateChanged=!0,this.haveStatePropsBeenPrecalculated=!1,this.statePropsPrecalculationError=null,this.renderedElement=null,this.finalMapDispatchToProps=null,this.finalMapStateToProps=null},T.prototype.handleChange=function(){if(this.unsubscribe){var A=this.store.getState(),M=this.state.storeState;if(!s||M!==A){if(s&&!this.doStatePropsDependOnOwnProps){var t=E(this.updateStatePropsIfNeeded,this);if(!t)return;t===Y&&(this.statePropsPrecalculationError=Y.value),this.haveStatePropsBeenPrecalculated=!0}this.hasStoreStateChanged=!0,this.setState({storeState:A})}}},T.prototype.getWrappedInstance=function(){return(0,u.default)(y,"To access the wrapped instance, you need to specify { withRef: true } as the fourth argument of the connect() call."),this.refs.wrappedInstance},T.prototype.render=function(){var M=this.haveOwnPropsChanged,t=this.hasStoreStateChanged,I=this.haveStatePropsBeenPrecalculated,g=this.statePropsPrecalculationError,e=this.renderedElement;if(this.haveOwnPropsChanged=!1,this.hasStoreStateChanged=!1,this.haveStatePropsBeenPrecalculated=!1,this.statePropsPrecalculationError=null,g)throw g;var i=!0,T=!0;s&&e&&(i=t||M&&this.doStatePropsDependOnOwnProps,T=M&&this.doDispatchPropsDependOnOwnProps);var E=!1,N=!1;I?E=!0:i&&(E=this.updateStatePropsIfNeeded()),T&&(N=this.updateDispatchPropsIfNeeded());var C=!0;return C=!!(E||N||M)&&this.updateMergedPropsIfNeeded(),!C&&e?e:(y?this.renderedElement=(0,n.createElement)(A,o({},this.mergedProps,{ref:"wrappedInstance"})):this.renderedElement=(0,n.createElement)(A,this.mergedProps),this.renderedElement)},T}(n.Component);return I.displayName=t,I.WrappedComponent=A,I.contextTypes={store:c.default},I.propTypes={store:c.default},(0,j.default)(I,A)}}M.__esModule=!0;var o=Object.assign||function(A){for(var M=1;M should not have a "'+M+'" prop')}M.__esModule=!0,M.routes=M.route=M.components=M.component=M.history=void 0,M.falsy=I;var g=t(1),e=g.PropTypes.func,i=g.PropTypes.object,T=g.PropTypes.arrayOf,E=g.PropTypes.oneOfType,N=g.PropTypes.element,o=g.PropTypes.shape,n=g.PropTypes.string,C=(M.history=o({listen:e.isRequired,push:e.isRequired,replace:e.isRequired,go:e.isRequired,goBack:e.isRequired,goForward:e.isRequired}),M.component=E([e,n])),c=(M.components=E([C,i]),M.route=E([i,N]));M.routes=E([c,T(c)])},function(A,M,t){"use strict";function I(A){return A&&A.__esModule?A:{default:A}}function g(A){var M=A.match(/^https?:\/\/[^\/]*/);return null==M?A:A.substring(M[0].length)}function e(A){var M=g(A),t="",I="",e=M.indexOf("#");e!==-1&&(I=M.substring(e),M=M.substring(0,e));var i=M.indexOf("?");return i!==-1&&(t=M.substring(i),M=M.substring(0,i)),""===M&&(M="/"),{pathname:M,search:t,hash:I}}M.__esModule=!0,M.extractPath=g,M.parsePath=e;var i=t(23);I(i)},function(A,M,t){"use strict";function I(){g.attachRefs(this,this._currentElement)}var g=t(459),e={mountComponent:function(A,M,t,g){var e=A.mountComponent(M,t,g);return A._currentElement&&null!=A._currentElement.ref&&t.getReactMountReady().enqueue(I,A),e},unmountComponent:function(A){g.detachRefs(A,A._currentElement),A.unmountComponent()},receiveComponent:function(A,M,t,e){var i=A._currentElement;if(M!==i||e!==A._context){var T=g.shouldUpdateRefs(i,M);T&&g.detachRefs(A,i),A.receiveComponent(M,t,e),T&&A._currentElement&&null!=A._currentElement.ref&&t.getReactMountReady().enqueue(I,A)}},performUpdateIfNecessary:function(A,M){A.performUpdateIfNecessary(M)}};A.exports=e},function(A,M,t){"use strict";function I(A,M,t,I){this.dispatchConfig=A,this.dispatchMarker=M,this.nativeEvent=t;var g=this.constructor.Interface;for(var e in g)if(g.hasOwnProperty(e)){var T=g[e];T?this[e]=T(t):"target"===e?this.target=I:this[e]=t[e]}var E=null!=t.defaultPrevented?t.defaultPrevented:t.returnValue===!1;E?this.isDefaultPrevented=i.thatReturnsTrue:this.isDefaultPrevented=i.thatReturnsFalse,this.isPropagationStopped=i.thatReturnsFalse}var g=t(31),e=t(4),i=t(20),T=(t(3),{type:null,target:null,currentTarget:i.thatReturnsNull,eventPhase:null,bubbles:null,cancelable:null,timeStamp:function(A){return A.timeStamp||Date.now()},defaultPrevented:null,isTrusted:null});e(I.prototype,{preventDefault:function(){this.defaultPrevented=!0;var A=this.nativeEvent;A&&(A.preventDefault?A.preventDefault():A.returnValue=!1,this.isDefaultPrevented=i.thatReturnsTrue)},stopPropagation:function(){var A=this.nativeEvent;A&&(A.stopPropagation?A.stopPropagation():A.cancelBubble=!0,this.isPropagationStopped=i.thatReturnsTrue)},persist:function(){this.isPersistent=i.thatReturnsTrue},isPersistent:i.thatReturnsFalse,destructor:function(){var A=this.constructor.Interface;for(var M in A)this[M]=null;this.dispatchConfig=null,this.dispatchMarker=null,this.nativeEvent=null}}),I.Interface=T,I.augmentClass=function(A,M){var t=this,I=Object.create(t.prototype);e(I,A.prototype),A.prototype=I,A.prototype.constructor=A,A.Interface=e({},t.Interface,M),A.augmentClass=t.augmentClass,g.addPoolingTo(A,g.fourArgumentPooler)},g.addPoolingTo(I,g.fourArgumentPooler),A.exports=I},function(A,M){"use strict";M.default=function(A,M){var t={};for(var I in A)M.indexOf(I)>=0||Object.prototype.hasOwnProperty.call(A,I)&&(t[I]=A[I]);return t},M.__esModule=!0},function(A,M){"use strict";function t(A){return A&&A.ownerDocument||document}M.__esModule=!0,M.default=t,A.exports=M.default},function(A,M,t){"use strict";var I=t(32),g=function(){var A=I&&document.documentElement;return A&&A.contains?function(A,M){return A.contains(M)}:A&&A.compareDocumentPosition?function(A,M){return A===M||!!(16&A.compareDocumentPosition(M))}:function(A,M){if(M)do if(M===A)return!0;while(M=M.parentNode);return!1}}();A.exports=g},function(A,M){function t(A){return"number"==typeof A&&A>-1&&A%1==0&&A<=I}var I=9007199254740991;A.exports=t},function(A,M,t){(function(A){!function(M,t){A.exports=t()}(this,function(){"use strict";function M(){return aI.apply(null,arguments)}function t(A){aI=A}function I(A){return A instanceof Array||"[object Array]"===Object.prototype.toString.call(A)}function g(A){return null!=A&&"[object Object]"===Object.prototype.toString.call(A)}function e(A){var M;for(M in A)return!1;return!0}function i(A){return"number"==typeof A||"[object Number]"===Object.prototype.toString.call(A)}function T(A){return A instanceof Date||"[object Date]"===Object.prototype.toString.call(A)}function E(A,M){var t,I=[];for(t=0;t0)for(t in rI)I=rI[t],g=M[I],B(g)||(A[I]=g);return A}function r(A){Q(this,A),this._d=new Date(null!=A._d?A._d.getTime():NaN),this.isValid()||(this._d=new Date(NaN)),sI===!1&&(sI=!0,M.updateOffset(this),sI=!1)}function s(A){return A instanceof r||null!=A&&null!=A._isAMomentObject}function x(A){return A<0?Math.ceil(A)||0:Math.floor(A)}function j(A){var M=+A,t=0;return 0!==M&&isFinite(M)&&(t=x(M)),t}function y(A,M,t){var I,g=Math.min(A.length,M.length),e=Math.abs(A.length-M.length),i=0;for(I=0;I0?"future":"past"];return l(t)?t(M):t.replace(/%s/i,M)}function F(A,M){var t=A.toLowerCase();SI[t]=SI[t+"s"]=SI[M]=A}function f(A){return"string"==typeof A?SI[A]||SI[A.toLowerCase()]:void 0}function k(A){var M,t,I={};for(t in A)N(A,t)&&(M=f(t),M&&(I[M]=A[t]));return I}function R(A,M){zI[A]=M}function J(A){var M=[];for(var t in A)M.push({unit:t,priority:zI[t]});return M.sort(function(A,M){return A.priority-M.priority}),M}function G(A,t){return function(I){return null!=I?(b(this,A,I),M.updateOffset(this,t),this):H(this,A)}}function H(A,M){return A.isValid()?A._d["get"+(A._isUTC?"UTC":"")+M]():NaN}function b(A,M,t){A.isValid()&&A._d["set"+(A._isUTC?"UTC":"")+M](t)}function X(A){return A=f(A),l(this[A])?this[A]():this}function v(A,M){if("object"==typeof A){A=k(A);for(var t=J(A),I=0;I=0;return(e?t?"+":"":"-")+Math.pow(10,Math.max(0,g)).toString().substr(1)+I}function V(A,M,t,I){var g=I;"string"==typeof I&&(g=function(){return this[I]()}),A&&(mI[A]=g),M&&(mI[M[0]]=function(){return W(g.apply(this,arguments),M[1],M[2])}),t&&(mI[t]=function(){return this.localeData().ordinal(g.apply(this,arguments),A)})}function P(A){return A.match(/\[[\s\S]/)?A.replace(/^\[|\]$/g,""):A.replace(/\\/g,"")}function Z(A){var M,t,I=A.match(pI);for(M=0,t=I.length;M=0&&UI.test(A);)A=A.replace(UI,t),UI.lastIndex=0,I-=1;return A}function _(A,M,t){$I[A]=l(M)?M:function(A,I){return A&&t?t:M}}function $(A,M){return N($I,A)?$I[A](M._strict,M._locale):new RegExp(AA(A))}function AA(A){return MA(A.replace("\\","").replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(A,M,t,I,g){return M||t||I||g}))}function MA(A){return A.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}function tA(A,M){var t,I=M;for("string"==typeof A&&(A=[A]),i(M)&&(I=function(A,t){t[M]=j(A)}),t=0;t=0&&isFinite(T.getFullYear())&&T.setFullYear(A),T}function xA(A){var M=new Date(Date.UTC.apply(null,arguments));return A<100&&A>=0&&isFinite(M.getUTCFullYear())&&M.setUTCFullYear(A),M}function jA(A,M,t){var I=7+M-t,g=(7+xA(A,0,I).getUTCDay()-M)%7;return-g+I-1}function yA(A,M,t,I,g){var e,i,T=(7+t-I)%7,E=jA(A,I,g),N=1+7*(M-1)+T+E;return N<=0?(e=A-1,i=BA(e)+N):N>BA(A)?(e=A+1,i=N-BA(A)):(e=A,i=N),{year:e,dayOfYear:i}}function uA(A,M,t){var I,g,e=jA(A.year(),M,t),i=Math.floor((A.dayOfYear()-e-1)/7)+1;return i<1?(g=A.year()-1,I=i+wA(g,M,t)):i>wA(A.year(),M,t)?(I=i-wA(A.year(),M,t),g=A.year()+1):(g=A.year(),I=i),{week:I,year:g}}function wA(A,M,t){var I=jA(A,M,t),g=jA(A+1,M,t);return(BA(A)-I+g)/7}function LA(A){return uA(A,this._week.dow,this._week.doy).week}function lA(){return this._week.dow}function YA(){return this._week.doy}function dA(A){var M=this.localeData().week(this);return null==A?M:this.add(7*(A-M),"d")}function hA(A){var M=uA(this,1,4).week;return null==A?M:this.add(7*(A-M),"d")}function SA(A,M){return"string"!=typeof A?A:isNaN(A)?(A=M.weekdaysParse(A),"number"==typeof A?A:null):parseInt(A,10)}function zA(A,M){return"string"==typeof A?M.weekdaysParse(A)%7||7:isNaN(A)?null:A}function pA(A,M){return A?I(this._weekdays)?this._weekdays[A.day()]:this._weekdays[this._weekdays.isFormat.test(M)?"format":"standalone"][A.day()]:this._weekdays}function UA(A){return A?this._weekdaysShort[A.day()]:this._weekdaysShort}function OA(A){return A?this._weekdaysMin[A.day()]:this._weekdaysMin}function mA(A,M,t){var I,g,e,i=A.toLocaleLowerCase();if(!this._weekdaysParse)for(this._weekdaysParse=[],this._shortWeekdaysParse=[],this._minWeekdaysParse=[],I=0;I<7;++I)e=n([2e3,1]).day(I),this._minWeekdaysParse[I]=this.weekdaysMin(e,"").toLocaleLowerCase(),this._shortWeekdaysParse[I]=this.weekdaysShort(e,"").toLocaleLowerCase(),this._weekdaysParse[I]=this.weekdays(e,"").toLocaleLowerCase();return t?"dddd"===M?(g=og.call(this._weekdaysParse,i),g!==-1?g:null):"ddd"===M?(g=og.call(this._shortWeekdaysParse,i),g!==-1?g:null):(g=og.call(this._minWeekdaysParse,i),g!==-1?g:null):"dddd"===M?(g=og.call(this._weekdaysParse,i),g!==-1?g:(g=og.call(this._shortWeekdaysParse,i),g!==-1?g:(g=og.call(this._minWeekdaysParse,i),g!==-1?g:null))):"ddd"===M?(g=og.call(this._shortWeekdaysParse,i),g!==-1?g:(g=og.call(this._weekdaysParse,i),g!==-1?g:(g=og.call(this._minWeekdaysParse,i),g!==-1?g:null))):(g=og.call(this._minWeekdaysParse,i),g!==-1?g:(g=og.call(this._weekdaysParse,i),g!==-1?g:(g=og.call(this._shortWeekdaysParse,i),g!==-1?g:null)))}function FA(A,M,t){var I,g,e;if(this._weekdaysParseExact)return mA.call(this,A,M,t);for(this._weekdaysParse||(this._weekdaysParse=[],this._minWeekdaysParse=[],this._shortWeekdaysParse=[],this._fullWeekdaysParse=[]),I=0;I<7;I++){if(g=n([2e3,1]).day(I),t&&!this._fullWeekdaysParse[I]&&(this._fullWeekdaysParse[I]=new RegExp("^"+this.weekdays(g,"").replace(".",".?")+"$","i"),this._shortWeekdaysParse[I]=new RegExp("^"+this.weekdaysShort(g,"").replace(".",".?")+"$","i"),this._minWeekdaysParse[I]=new RegExp("^"+this.weekdaysMin(g,"").replace(".",".?")+"$","i")),this._weekdaysParse[I]||(e="^"+this.weekdays(g,"")+"|^"+this.weekdaysShort(g,"")+"|^"+this.weekdaysMin(g,""),this._weekdaysParse[I]=new RegExp(e.replace(".",""),"i")),t&&"dddd"===M&&this._fullWeekdaysParse[I].test(A))return I;if(t&&"ddd"===M&&this._shortWeekdaysParse[I].test(A))return I;if(t&&"dd"===M&&this._minWeekdaysParse[I].test(A))return I;if(!t&&this._weekdaysParse[I].test(A))return I}}function fA(A){if(!this.isValid())return null!=A?this:NaN;var M=this._isUTC?this._d.getUTCDay():this._d.getDay();return null!=A?(A=SA(A,this.localeData()),this.add(A-M,"d")):M}function kA(A){if(!this.isValid())return null!=A?this:NaN;var M=(this.day()+7-this.localeData()._week.dow)%7;return null==A?M:this.add(A-M,"d")}function RA(A){if(!this.isValid())return null!=A?this:NaN;if(null!=A){var M=zA(A,this.localeData());return this.day(this.day()%7?M:M-7)}return this.day()||7}function JA(A){return this._weekdaysParseExact?(N(this,"_weekdaysRegex")||bA.call(this),A?this._weekdaysStrictRegex:this._weekdaysRegex):(N(this,"_weekdaysRegex")||(this._weekdaysRegex=jg),this._weekdaysStrictRegex&&A?this._weekdaysStrictRegex:this._weekdaysRegex)}function GA(A){return this._weekdaysParseExact?(N(this,"_weekdaysRegex")||bA.call(this),A?this._weekdaysShortStrictRegex:this._weekdaysShortRegex):(N(this,"_weekdaysShortRegex")||(this._weekdaysShortRegex=yg),this._weekdaysShortStrictRegex&&A?this._weekdaysShortStrictRegex:this._weekdaysShortRegex)}function HA(A){return this._weekdaysParseExact?(N(this,"_weekdaysRegex")||bA.call(this),A?this._weekdaysMinStrictRegex:this._weekdaysMinRegex):(N(this,"_weekdaysMinRegex")||(this._weekdaysMinRegex=ug),this._weekdaysMinStrictRegex&&A?this._weekdaysMinStrictRegex:this._weekdaysMinRegex)}function bA(){function A(A,M){return M.length-A.length}var M,t,I,g,e,i=[],T=[],E=[],N=[];for(M=0;M<7;M++)t=n([2e3,1]).day(M),I=this.weekdaysMin(t,""),g=this.weekdaysShort(t,""),e=this.weekdays(t,""),i.push(I),T.push(g),E.push(e),N.push(I),N.push(g),N.push(e);for(i.sort(A),T.sort(A),E.sort(A),N.sort(A),M=0;M<7;M++)T[M]=MA(T[M]),E[M]=MA(E[M]),N[M]=MA(N[M]);this._weekdaysRegex=new RegExp("^("+N.join("|")+")","i"),this._weekdaysShortRegex=this._weekdaysRegex,this._weekdaysMinRegex=this._weekdaysRegex,this._weekdaysStrictRegex=new RegExp("^("+E.join("|")+")","i"),this._weekdaysShortStrictRegex=new RegExp("^("+T.join("|")+")","i"),this._weekdaysMinStrictRegex=new RegExp("^("+i.join("|")+")","i")}function XA(){return this.hours()%12||12}function vA(){return this.hours()||24}function WA(A,M){V(A,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),M)})}function VA(A,M){return M._meridiemParse}function PA(A){return"p"===(A+"").toLowerCase().charAt(0)}function ZA(A,M,t){return A>11?t?"pm":"PM":t?"am":"AM"}function KA(A){return A?A.toLowerCase().replace("_","-"):A}function qA(A){for(var M,t,I,g,e=0;e0;){if(I=_A(g.slice(0,M).join("-")))return I;if(t&&t.length>=M&&y(g,t,!0)>=M-1)break;M--}e++}return null}function _A(M){var t=null;if(!dg[M]&&"undefined"!=typeof A&&A&&A.exports)try{t=wg._abbr,!function(){var A=new Error('Cannot find module "./locale"');throw A.code="MODULE_NOT_FOUND",A}(),$A(t)}catch(A){}return dg[M]}function $A(A,M){var t;return A&&(t=B(M)?tM(A):AM(A,M),t&&(wg=t)),wg._abbr}function AM(A,M){if(null!==M){var t=Yg;if(M.abbr=A,null!=dg[A])L("defineLocaleOverride","use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale See http://momentjs.com/guides/#/warnings/define-locale/ for more info."),t=dg[A]._config;else if(null!=M.parentLocale){if(null==dg[M.parentLocale])return hg[M.parentLocale]||(hg[M.parentLocale]=[]),hg[M.parentLocale].push({name:A,config:M}),null;t=dg[M.parentLocale]._config}return dg[A]=new h(d(t,M)),hg[A]&&hg[A].forEach(function(A){AM(A.name,A.config)}),$A(A),dg[A]}return delete dg[A],null}function MM(A,M){if(null!=M){var t,I=Yg;null!=dg[A]&&(I=dg[A]._config),M=d(I,M),t=new h(M),t.parentLocale=dg[A],dg[A]=t,$A(A)}else null!=dg[A]&&(null!=dg[A].parentLocale?dg[A]=dg[A].parentLocale:null!=dg[A]&&delete dg[A]);return dg[A]}function tM(A){var M;if(A&&A._locale&&A._locale._abbr&&(A=A._locale._abbr),!A)return wg;if(!I(A)){if(M=_A(A))return M;A=[A]}return qA(A)}function IM(){return uI(dg)}function gM(A){var M,t=A._a;return t&&c(A).overflow===-2&&(M=t[tg]<0||t[tg]>11?tg:t[Ig]<1||t[Ig]>eA(t[Mg],t[tg])?Ig:t[gg]<0||t[gg]>24||24===t[gg]&&(0!==t[eg]||0!==t[ig]||0!==t[Tg])?gg:t[eg]<0||t[eg]>59?eg:t[ig]<0||t[ig]>59?ig:t[Tg]<0||t[Tg]>999?Tg:-1,c(A)._overflowDayOfYear&&(MIg)&&(M=Ig),c(A)._overflowWeeks&&M===-1&&(M=Eg),c(A)._overflowWeekday&&M===-1&&(M=Ng),c(A).overflow=M),A}function eM(A){var M,t,I,g,e,i,T=A._i,E=Sg.exec(T)||zg.exec(T);if(E){for(c(A).iso=!0,M=0,t=Ug.length;MBA(g)&&(c(A)._overflowDayOfYear=!0),t=xA(g,0,A._dayOfYear),A._a[tg]=t.getUTCMonth(),A._a[Ig]=t.getUTCDate()),M=0;M<3&&null==A._a[M];++M)A._a[M]=e[M]=I[M];for(;M<7;M++)A._a[M]=e[M]=null==A._a[M]?2===M?1:0:A._a[M];24===A._a[gg]&&0===A._a[eg]&&0===A._a[ig]&&0===A._a[Tg]&&(A._nextDay=!0,A._a[gg]=0),A._d=(A._useUTC?xA:sA).apply(null,e),null!=A._tzm&&A._d.setUTCMinutes(A._d.getUTCMinutes()-A._tzm),A._nextDay&&(A._a[gg]=24)}}function oM(A){var M,t,I,g,e,i,T,E;if(M=A._w,null!=M.GG||null!=M.W||null!=M.E)e=1,i=4,t=TM(M.GG,A._a[Mg],uA(sM(),1,4).year),I=TM(M.W,1),g=TM(M.E,1),(g<1||g>7)&&(E=!0);else{e=A._locale._week.dow,i=A._locale._week.doy;var N=uA(sM(),e,i);t=TM(M.gg,A._a[Mg],N.year),I=TM(M.w,N.week),null!=M.d?(g=M.d,(g<0||g>6)&&(E=!0)):null!=M.e?(g=M.e+e,(M.e<0||M.e>6)&&(E=!0)):g=e}I<1||I>wA(t,e,i)?c(A)._overflowWeeks=!0:null!=E?c(A)._overflowWeekday=!0:(T=yA(t,I,g,e,i),A._a[Mg]=T.year,A._dayOfYear=T.dayOfYear)}function nM(A){if(A._f===M.ISO_8601)return void eM(A);A._a=[],c(A).empty=!0;var t,I,g,e,i,T=""+A._i,E=T.length,N=0;for(g=q(A._f,A._locale).match(pI)||[],t=0;t0&&c(A).unusedInput.push(i),T=T.slice(T.indexOf(I)+I.length),N+=I.length),mI[e]?(I?c(A).empty=!1:c(A).unusedTokens.push(e),gA(e,I,A)):A._strict&&!I&&c(A).unusedTokens.push(e);c(A).charsLeftOver=E-N,T.length>0&&c(A).unusedInput.push(T),A._a[gg]<=12&&c(A).bigHour===!0&&A._a[gg]>0&&(c(A).bigHour=void 0),c(A).parsedDateParts=A._a.slice(0),c(A).meridiem=A._meridiem,A._a[gg]=CM(A._locale,A._a[gg],A._meridiem),NM(A),gM(A)}function CM(A,M,t){var I;return null==t?M:null!=A.meridiemHour?A.meridiemHour(M,t):null!=A.isPM?(I=A.isPM(t),I&&M<12&&(M+=12),I||12!==M||(M=0),M):M}function cM(A){var M,t,I,g,e;if(0===A._f.length)return c(A).invalidFormat=!0,void(A._d=new Date(NaN));for(g=0;gthis.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()}function fM(){if(!B(this._isDSTShifted))return this._isDSTShifted;var A={};if(Q(A,this),A=BM(A),A._a){var M=A._isUTC?n(A._a):sM(A._a);this._isDSTShifted=this.isValid()&&y(A._a,M.toArray())>0}else this._isDSTShifted=!1;return this._isDSTShifted}function kM(){return!!this.isValid()&&!this._isUTC}function RM(){return!!this.isValid()&&this._isUTC}function JM(){return!!this.isValid()&&(this._isUTC&&0===this._offset)}function GM(A,M){var t,I,g,e=A,T=null;return wM(A)?e={ms:A._milliseconds,d:A._days,M:A._months}:i(A)?(e={},M?e[M]=A:e.milliseconds=A):(T=Jg.exec(A))?(t="-"===T[1]?-1:1,e={y:0,d:j(T[Ig])*t,h:j(T[gg])*t,m:j(T[eg])*t,s:j(T[ig])*t,ms:j(LM(1e3*T[Tg]))*t}):(T=Gg.exec(A))?(t="-"===T[1]?-1:1,e={y:HM(T[2],t),M:HM(T[3],t),w:HM(T[4],t),d:HM(T[5],t),h:HM(T[6],t),m:HM(T[7],t),s:HM(T[8],t)}):null==e?e={}:"object"==typeof e&&("from"in e||"to"in e)&&(g=XM(sM(e.from),sM(e.to)),e={},e.ms=g.milliseconds,e.M=g.months),I=new uM(e),wM(A)&&N(A,"_locale")&&(I._locale=A._locale),I}function HM(A,M){var t=A&&parseFloat(A.replace(",","."));return(isNaN(t)?0:t)*M}function bM(A,M){var t={milliseconds:0,months:0};return t.months=M.month()-A.month()+12*(M.year()-A.year()),A.clone().add(t.months,"M").isAfter(M)&&--t.months,t.milliseconds=+M-+A.clone().add(t.months,"M"),t}function XM(A,M){var t;return A.isValid()&&M.isValid()?(M=dM(M,A),A.isBefore(M)?t=bM(A,M):(t=bM(M,A),t.milliseconds=-t.milliseconds,t.months=-t.months),t):{milliseconds:0,months:0}}function vM(A,M){return function(t,I){var g,e;return null===I||isNaN(+I)||(L(M,"moment()."+M+"(period, number) is deprecated. Please use moment()."+M+"(number, period). See http://momentjs.com/guides/#/warnings/add-inverted-param/ for more info."),e=t,t=I,I=e),t="string"==typeof t?+t:t,g=GM(t,I),WM(this,g,A),this}}function WM(A,t,I,g){var e=t._milliseconds,i=LM(t._days),T=LM(t._months);A.isValid()&&(g=null==g||g,e&&A._d.setTime(A._d.valueOf()+e*I),i&&b(A,"Date",H(A,"Date")+i*I),T&&oA(A,H(A,"Month")+T*I),g&&M.updateOffset(A,i||T))}function VM(A,M){var t=A.diff(M,"days",!0);return t<-6?"sameElse":t<-1?"lastWeek":t<0?"lastDay":t<1?"sameDay":t<2?"nextDay":t<7?"nextWeek":"sameElse"}function PM(A,t){var I=A||sM(),g=dM(I,this).startOf("day"),e=M.calendarFormat(this,g)||"sameElse",i=t&&(l(t[e])?t[e].call(this,I):t[e]);return this.format(i||this.localeData().calendar(e,this,sM(I)))}function ZM(){return new r(this)}function KM(A,M){var t=s(A)?A:sM(A);return!(!this.isValid()||!t.isValid())&&(M=f(B(M)?"millisecond":M),"millisecond"===M?this.valueOf()>t.valueOf():t.valueOf()e&&(M=e),pt.call(this,A,M,t,I,g))}function pt(A,M,t,I,g){var e=yA(A,M,t,I,g),i=xA(e.year,0,e.dayOfYear);return this.year(i.getUTCFullYear()),this.month(i.getUTCMonth()),this.date(i.getUTCDate()),this}function Ut(A){return null==A?Math.ceil((this.month()+1)/3):this.month(3*(A-1)+this.month()%3)}function Ot(A){var M=Math.round((this.clone().startOf("day")-this.clone().startOf("year"))/864e5)+1;return null==A?M:this.add(A-M,"d")}function mt(A,M){M[Tg]=j(1e3*("0."+A))}function Ft(){return this._isUTC?"UTC":""}function ft(){return this._isUTC?"Coordinated Universal Time":""}function kt(A){return sM(1e3*A)}function Rt(){return sM.apply(null,arguments).parseZone()}function Jt(A){return A}function Gt(A,M,t,I){var g=tM(),e=n().set(I,M);return g[t](e,A)}function Ht(A,M,t){if(i(A)&&(M=A,A=void 0),A=A||"",null!=M)return Gt(A,M,t,"month");var I,g=[];for(I=0;I<12;I++)g[I]=Gt(A,I,t,"month");return g}function bt(A,M,t,I){"boolean"==typeof A?(i(M)&&(t=M,M=void 0),M=M||""):(M=A,t=M,A=!1,i(M)&&(t=M,M=void 0),M=M||"");var g=tM(),e=A?g._week.dow:0;if(null!=t)return Gt(M,(t+e)%7,I,"day");var T,E=[];for(T=0;T<7;T++)E[T]=Gt(M,(T+e)%7,I,"day");return E}function Xt(A,M){return Ht(A,M,"months")}function vt(A,M){return Ht(A,M,"monthsShort")}function Wt(A,M,t){return bt(A,M,t,"weekdays")}function Vt(A,M,t){return bt(A,M,t,"weekdaysShort")}function Pt(A,M,t){return bt(A,M,t,"weekdaysMin")}function Zt(){var A=this._data;return this._milliseconds=_g(this._milliseconds),this._days=_g(this._days),this._months=_g(this._months),A.milliseconds=_g(A.milliseconds),A.seconds=_g(A.seconds),A.minutes=_g(A.minutes),A.hours=_g(A.hours),A.months=_g(A.months),A.years=_g(A.years),this}function Kt(A,M,t,I){var g=GM(M,t);return A._milliseconds+=I*g._milliseconds,A._days+=I*g._days,A._months+=I*g._months,A._bubble()}function qt(A,M){return Kt(this,A,M,1)}function _t(A,M){return Kt(this,A,M,-1)}function $t(A){return A<0?Math.floor(A):Math.ceil(A)}function AI(){var A,M,t,I,g,e=this._milliseconds,i=this._days,T=this._months,E=this._data;return e>=0&&i>=0&&T>=0||e<=0&&i<=0&&T<=0||(e+=864e5*$t(tI(T)+i),i=0,T=0),E.milliseconds=e%1e3,A=x(e/1e3),E.seconds=A%60,M=x(A/60),E.minutes=M%60,t=x(M/60),E.hours=t%24,i+=x(t/24),g=x(MI(i)),T+=g,i-=$t(tI(g)),I=x(T/12),T%=12,E.days=i,E.months=T,E.years=I,this}function MI(A){return 4800*A/146097}function tI(A){return 146097*A/4800}function II(A){var M,t,I=this._milliseconds;if(A=f(A),"month"===A||"year"===A)return M=this._days+I/864e5,t=this._months+MI(M),"month"===A?t:t/12;switch(M=this._days+Math.round(tI(this._months)),A){case"week":return M/7+I/6048e5;case"day":return M+I/864e5;case"hour":return 24*M+I/36e5;case"minute":return 1440*M+I/6e4;case"second":return 86400*M+I/1e3;case"millisecond":return Math.floor(864e5*M)+I;default:throw new Error("Unknown unit "+A)}}function gI(){return this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*j(this._months/12)}function eI(A){return function(){return this.as(A)}}function iI(A){return A=f(A),this[A+"s"]()}function TI(A){return function(){return this._data[A]}}function EI(){return x(this.days()/7)}function NI(A,M,t,I,g){return g.relativeTime(M||1,!!t,A,I)}function oI(A,M,t){var I=GM(A).abs(),g=De(I.as("s")),e=De(I.as("m")),i=De(I.as("h")),T=De(I.as("d")),E=De(I.as("M")),N=De(I.as("y")),o=g0,o[4]=t,NI.apply(null,o)}function nI(A){return void 0===A?De:"function"==typeof A&&(De=A,!0)}function CI(A,M){return void 0!==ae[A]&&(void 0===M?ae[A]:(ae[A]=M,!0))}function cI(A){var M=this.localeData(),t=oI(this,!A,M);return A&&(t=M.pastFuture(+this,t)),M.postformat(t)}function DI(){var A,M,t,I=Be(this._milliseconds)/1e3,g=Be(this._days),e=Be(this._months);A=x(I/60),M=x(A/60),I%=60,A%=60,t=x(e/12),e%=12;var i=t,T=e,E=g,N=M,o=A,n=I,C=this.asSeconds();return C?(C<0?"-":"")+"P"+(i?i+"Y":"")+(T?T+"M":"")+(E?E+"D":"")+(N||o||n?"T":"")+(N?N+"H":"")+(o?o+"M":"")+(n?n+"S":""):"P0D"}var aI,BI;BI=Array.prototype.some?Array.prototype.some:function(A){for(var M=Object(this),t=M.length>>>0,I=0;I68?1900:2e3)};var Bg=G("FullYear",!0);V("w",["ww",2],"wo","week"),V("W",["WW",2],"Wo","isoWeek"),F("week","w"),F("isoWeek","W"),R("week",5),R("isoWeek",5),_("w",GI),_("ww",GI,fI),_("W",GI),_("WW",GI,fI),IA(["w","ww","W","WW"],function(A,M,t,I){M[I.substr(0,1)]=j(A)});var Qg={dow:0,doy:6};V("d",0,"do","day"),V("dd",0,0,function(A){return this.localeData().weekdaysMin(this,A)}),V("ddd",0,0,function(A){return this.localeData().weekdaysShort(this,A)}),V("dddd",0,0,function(A){return this.localeData().weekdays(this,A)}),V("e",0,0,"weekday"),V("E",0,0,"isoWeekday"),F("day","d"),F("weekday","e"),F("isoWeekday","E"),R("day",11),R("weekday",11),R("isoWeekday",11),_("d",GI),_("e",GI),_("E",GI),_("dd",function(A,M){return M.weekdaysMinRegex(A)}),_("ddd",function(A,M){return M.weekdaysShortRegex(A)}),_("dddd",function(A,M){return M.weekdaysRegex(A)}),IA(["dd","ddd","dddd"],function(A,M,t,I){var g=t._locale.weekdaysParse(A,I,t._strict);null!=g?M.d=g:c(t).invalidWeekday=A}),IA(["d","e","E"],function(A,M,t,I){M[I]=j(A)});var rg="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),sg="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),xg="Su_Mo_Tu_We_Th_Fr_Sa".split("_"),jg=_I,yg=_I,ug=_I;V("H",["HH",2],0,"hour"),V("h",["hh",2],0,XA),V("k",["kk",2],0,vA),V("hmm",0,0,function(){return""+XA.apply(this)+W(this.minutes(),2)}),V("hmmss",0,0,function(){return""+XA.apply(this)+W(this.minutes(),2)+W(this.seconds(),2)}),V("Hmm",0,0,function(){return""+this.hours()+W(this.minutes(),2)}),V("Hmmss",0,0,function(){return""+this.hours()+W(this.minutes(),2)+W(this.seconds(),2)}),WA("a",!0),WA("A",!1),F("hour","h"),R("hour",13),_("a",VA),_("A",VA),_("H",GI),_("h",GI),_("HH",GI,fI),_("hh",GI,fI),_("hmm",HI),_("hmmss",bI),_("Hmm",HI),_("Hmmss",bI),tA(["H","HH"],gg),tA(["a","A"],function(A,M,t){t._isPm=t._locale.isPM(A),t._meridiem=A}),tA(["h","hh"],function(A,M,t){M[gg]=j(A),c(t).bigHour=!0}),tA("hmm",function(A,M,t){var I=A.length-2;M[gg]=j(A.substr(0,I)),M[eg]=j(A.substr(I)),c(t).bigHour=!0}),tA("hmmss",function(A,M,t){var I=A.length-4,g=A.length-2;M[gg]=j(A.substr(0,I)),M[eg]=j(A.substr(I,2)),M[ig]=j(A.substr(g)),c(t).bigHour=!0}),tA("Hmm",function(A,M,t){var I=A.length-2;M[gg]=j(A.substr(0,I)),M[eg]=j(A.substr(I))}),tA("Hmmss",function(A,M,t){var I=A.length-4,g=A.length-2;M[gg]=j(A.substr(0,I)),M[eg]=j(A.substr(I,2)),M[ig]=j(A.substr(g))});var wg,Lg=/[ap]\.?m?\.?/i,lg=G("Hours",!0),Yg={calendar:wI,longDateFormat:LI,invalidDate:lI,ordinal:YI,ordinalParse:dI,relativeTime:hI,months:Cg,monthsShort:cg,week:Qg,weekdays:rg,weekdaysMin:xg,weekdaysShort:sg,meridiemParse:Lg},dg={},hg={},Sg=/^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,zg=/^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,pg=/Z|[+-]\d\d(?::?\d\d)?/,Ug=[["YYYYYY-MM-DD",/[+-]\d{6}-\d\d-\d\d/],["YYYY-MM-DD",/\d{4}-\d\d-\d\d/],["GGGG-[W]WW-E",/\d{4}-W\d\d-\d/],["GGGG-[W]WW",/\d{4}-W\d\d/,!1],["YYYY-DDD",/\d{4}-\d{3}/],["YYYY-MM",/\d{4}-\d\d/,!1],["YYYYYYMMDD",/[+-]\d{10}/],["YYYYMMDD",/\d{8}/],["GGGG[W]WWE",/\d{4}W\d{3}/],["GGGG[W]WW",/\d{4}W\d{2}/,!1],["YYYYDDD",/\d{7}/]],Og=[["HH:mm:ss.SSSS",/\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss,SSSS",/\d\d:\d\d:\d\d,\d+/],["HH:mm:ss",/\d\d:\d\d:\d\d/],["HH:mm",/\d\d:\d\d/],["HHmmss.SSSS",/\d\d\d\d\d\d\.\d+/],["HHmmss,SSSS",/\d\d\d\d\d\d,\d+/],["HHmmss",/\d\d\d\d\d\d/],["HHmm",/\d\d\d\d/],["HH",/\d\d/]],mg=/^\/?Date\((\-?\d+)/i;M.createFromInputFallback=w("value provided is not in a recognized ISO format. moment construction falls back to js Date(), which is not reliable across all browsers and versions. Non ISO date formats are discouraged and will be removed in an upcoming major release. Please refer to http://momentjs.com/guides/#/warnings/js-date/ for more info.",function(A){A._d=new Date(A._i+(A._useUTC?" UTC":""))}),M.ISO_8601=function(){};var Fg=w("moment().min is deprecated, use moment.max instead. http://momentjs.com/guides/#/warnings/min-max/",function(){var A=sM.apply(null,arguments);return this.isValid()&&A.isValid()?Athis?this:A:a()}),kg=function(){return Date.now?Date.now():+new Date};lM("Z",":"),lM("ZZ",""),_("Z",KI),_("ZZ",KI),tA(["Z","ZZ"],function(A,M,t){t._useUTC=!0,t._tzm=YM(KI,A)});var Rg=/([\+\-]|\d\d)/gi;M.updateOffset=function(){};var Jg=/^(\-)?(?:(\d*)[. ])?(\d+)\:(\d+)(?:\:(\d+)(\.\d*)?)?$/,Gg=/^(-)?P(?:(-?[0-9,.]*)Y)?(?:(-?[0-9,.]*)M)?(?:(-?[0-9,.]*)W)?(?:(-?[0-9,.]*)D)?(?:T(?:(-?[0-9,.]*)H)?(?:(-?[0-9,.]*)M)?(?:(-?[0-9,.]*)S)?)?$/;GM.fn=uM.prototype;var Hg=vM(1,"add"),bg=vM(-1,"subtract");M.defaultFormat="YYYY-MM-DDTHH:mm:ssZ",M.defaultFormatUtc="YYYY-MM-DDTHH:mm:ss[Z]";var Xg=w("moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.",function(A){return void 0===A?this.localeData():this.locale(A)});V(0,["gg",2],0,function(){return this.weekYear()%100}),V(0,["GG",2],0,function(){return this.isoWeekYear()%100}),lt("gggg","weekYear"),lt("ggggg","weekYear"),lt("GGGG","isoWeekYear"),lt("GGGGG","isoWeekYear"),F("weekYear","gg"),F("isoWeekYear","GG"),R("weekYear",1),R("isoWeekYear",1),_("G",PI),_("g",PI),_("GG",GI,fI),_("gg",GI,fI),_("GGGG",vI,RI),_("gggg",vI,RI),_("GGGGG",WI,JI),_("ggggg",WI,JI),IA(["gggg","ggggg","GGGG","GGGGG"],function(A,M,t,I){M[I.substr(0,2)]=j(A)}),IA(["gg","GG"],function(A,t,I,g){t[g]=M.parseTwoDigitYear(A)}),V("Q",0,"Qo","quarter"),F("quarter","Q"),R("quarter",7),_("Q",FI),tA("Q",function(A,M){M[tg]=3*(j(A)-1)}),V("D",["DD",2],"Do","date"),F("date","D"),R("date",9),_("D",GI),_("DD",GI,fI),_("Do",function(A,M){return A?M._ordinalParse:M._ordinalParseLenient}),tA(["D","DD"],Ig),tA("Do",function(A,M){M[Ig]=j(A.match(GI)[0],10)});var vg=G("Date",!0);V("DDD",["DDDD",3],"DDDo","dayOfYear"),F("dayOfYear","DDD"),R("dayOfYear",4),_("DDD",XI),_("DDDD",kI),tA(["DDD","DDDD"],function(A,M,t){t._dayOfYear=j(A)}),V("m",["mm",2],0,"minute"),F("minute","m"),R("minute",14),_("m",GI),_("mm",GI,fI),tA(["m","mm"],eg);var Wg=G("Minutes",!1);V("s",["ss",2],0,"second"),F("second","s"),R("second",15),_("s",GI),_("ss",GI,fI),tA(["s","ss"],ig);var Vg=G("Seconds",!1);V("S",0,0,function(){return~~(this.millisecond()/100)}),V(0,["SS",2],0,function(){return~~(this.millisecond()/10)}),V(0,["SSS",3],0,"millisecond"),V(0,["SSSS",4],0,function(){return 10*this.millisecond()}),V(0,["SSSSS",5],0,function(){return 100*this.millisecond()}),V(0,["SSSSSS",6],0,function(){return 1e3*this.millisecond()}),V(0,["SSSSSSS",7],0,function(){return 1e4*this.millisecond()}),V(0,["SSSSSSSS",8],0,function(){return 1e5*this.millisecond()}),V(0,["SSSSSSSSS",9],0,function(){return 1e6*this.millisecond()}),F("millisecond","ms"),R("millisecond",16),_("S",XI,FI),_("SS",XI,fI),_("SSS",XI,kI);var Pg;for(Pg="SSSS";Pg.length<=9;Pg+="S")_(Pg,VI);for(Pg="S";Pg.length<=9;Pg+="S")tA(Pg,mt);var Zg=G("Milliseconds",!1);V("z",0,0,"zoneAbbr"),V("zz",0,0,"zoneName");var Kg=r.prototype;Kg.add=Hg,Kg.calendar=PM,Kg.clone=ZM,Kg.diff=tt,Kg.endOf=at,Kg.format=Tt,Kg.from=Et,Kg.fromNow=Nt,Kg.to=ot,Kg.toNow=nt,Kg.get=X,Kg.invalidAt=wt,Kg.isAfter=KM,Kg.isBefore=qM,Kg.isBetween=_M,Kg.isSame=$M,Kg.isSameOrAfter=At,Kg.isSameOrBefore=Mt,Kg.isValid=yt,Kg.lang=Xg,Kg.locale=Ct,Kg.localeData=ct,Kg.max=fg,Kg.min=Fg,Kg.parsingFlags=ut,Kg.set=v,Kg.startOf=Dt,Kg.subtract=bg,Kg.toArray=st,Kg.toObject=xt,Kg.toDate=rt,Kg.toISOString=et,Kg.inspect=it,Kg.toJSON=jt,Kg.toString=gt,Kg.unix=Qt,Kg.valueOf=Bt,Kg.creationData=Lt,Kg.year=Bg,Kg.isLeapYear=rA,Kg.weekYear=Yt,Kg.isoWeekYear=dt,Kg.quarter=Kg.quarters=Ut,Kg.month=nA,Kg.daysInMonth=CA,Kg.week=Kg.weeks=dA,Kg.isoWeek=Kg.isoWeeks=hA,Kg.weeksInYear=St,Kg.isoWeeksInYear=ht,Kg.date=vg,Kg.day=Kg.days=fA,Kg.weekday=kA,Kg.isoWeekday=RA,Kg.dayOfYear=Ot,Kg.hour=Kg.hours=lg,Kg.minute=Kg.minutes=Wg,Kg.second=Kg.seconds=Vg,Kg.millisecond=Kg.milliseconds=Zg,Kg.utcOffset=SM,Kg.utc=pM,Kg.local=UM,Kg.parseZone=OM,Kg.hasAlignedHourOffset=mM,Kg.isDST=FM,Kg.isLocal=kM,Kg.isUtcOffset=RM,Kg.isUtc=JM,Kg.isUTC=JM,Kg.zoneAbbr=Ft,Kg.zoneName=ft,Kg.dates=w("dates accessor is deprecated. Use date instead.",vg),Kg.months=w("months accessor is deprecated. Use month instead",nA),Kg.years=w("years accessor is deprecated. Use year instead",Bg),Kg.zone=w("moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/",zM),Kg.isDSTShifted=w("isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information",fM);var qg=h.prototype;qg.calendar=S,qg.longDateFormat=z,qg.invalidDate=p,qg.ordinal=U,qg.preparse=Jt,qg.postformat=Jt,qg.relativeTime=O,qg.pastFuture=m,qg.set=Y,qg.months=iA,qg.monthsShort=TA,qg.monthsParse=NA,qg.monthsRegex=DA,qg.monthsShortRegex=cA,qg.week=LA,qg.firstDayOfYear=YA,qg.firstDayOfWeek=lA,qg.weekdays=pA,qg.weekdaysMin=OA,qg.weekdaysShort=UA,qg.weekdaysParse=FA,qg.weekdaysRegex=JA,qg.weekdaysShortRegex=GA,qg.weekdaysMinRegex=HA,qg.isPM=PA,qg.meridiem=ZA,$A("en",{ordinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(A){var M=A%10,t=1===j(A%100/10)?"th":1===M?"st":2===M?"nd":3===M?"rd":"th";return A+t}}),M.lang=w("moment.lang is deprecated. Use moment.locale instead.",$A),M.langData=w("moment.langData is deprecated. Use moment.localeData instead.",tM);var _g=Math.abs,$g=eI("ms"),Ae=eI("s"),Me=eI("m"),te=eI("h"),Ie=eI("d"),ge=eI("w"),ee=eI("M"),ie=eI("y"),Te=TI("milliseconds"),Ee=TI("seconds"),Ne=TI("minutes"),oe=TI("hours"),ne=TI("days"),Ce=TI("months"),ce=TI("years"),De=Math.round,ae={s:45,m:45,h:22,d:26,M:11},Be=Math.abs,Qe=uM.prototype;return Qe.abs=Zt,Qe.add=qt,Qe.subtract=_t,Qe.as=II,Qe.asMilliseconds=$g,Qe.asSeconds=Ae,Qe.asMinutes=Me,Qe.asHours=te,Qe.asDays=Ie,Qe.asWeeks=ge,Qe.asMonths=ee,Qe.asYears=ie,Qe.valueOf=gI,Qe._bubble=AI,Qe.get=iI,Qe.milliseconds=Te,Qe.seconds=Ee,Qe.minutes=Ne,Qe.hours=oe,Qe.days=ne,Qe.weeks=EI,Qe.months=Ce,Qe.years=ce,Qe.humanize=cI,Qe.toISOString=DI,Qe.toString=DI,Qe.toJSON=DI,Qe.locale=Ct,Qe.localeData=ct,Qe.toIsoString=w("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",DI),Qe.lang=Xg,V("X",0,0,"unix"),V("x",0,0,"valueOf"),_("x",PI),_("X",qI),tA("X",function(A,M,t){t._d=new Date(1e3*parseFloat(A,10))}),tA("x",function(A,M,t){t._d=new Date(j(A))}),M.version="2.17.1",t(sM),M.fn=Kg,M.min=jM,M.max=yM,M.now=kg,M.utc=n,M.unix=kt,M.months=Xt,M.isDate=T,M.locale=$A,M.invalid=a,M.duration=GM,M.isMoment=s,M.weekdays=Wt,M.parseZone=Rt,M.localeData=tM,M.isDuration=wM,M.monthsShort=vt,M.weekdaysMin=Pt,M.defineLocale=AM,M.updateLocale=MM,M.locales=IM,M.weekdaysShort=Vt,M.normalizeUnits=f,M.relativeTimeRounding=nI,M.relativeTimeThreshold=CI,M.calendarFormat=VM,M.prototype=Kg,M})}).call(M,t(128)(A))},function(A,M,t){"use strict";var I=t(132).default,g=t(133).default,e=t(78).default;M.__esModule=!0;var i=function(A){return I(g({values:function(){var A=this;return e(this).map(function(M){return A[M]})}}),A)},T={SIZES:{large:"lg",medium:"md",small:"sm",xsmall:"xs",lg:"lg",md:"md",sm:"sm",xs:"xs"},GRID_COLUMNS:12},E=i({LARGE:"large",MEDIUM:"medium",SMALL:"small",XSMALL:"xsmall"});M.Sizes=E;var N=i({SUCCESS:"success",WARNING:"warning",DANGER:"danger",INFO:"info"});M.State=N;var o="default";M.DEFAULT=o;var n="primary";M.PRIMARY=n;var C="link";M.LINK=C;var c="inverse";M.INVERSE=c,M.default=T},function(A,M){"use strict";function t(){for(var A=arguments.length,M=Array(A),t=0;t0?void 0:(0,C.default)(!1),null!=o&&(e+=encodeURI(o))):"("===E?g+=1:")"===E?g-=1:":"===E.charAt(0)?(N=E.substring(1),o=M[N],null!=o||g>0?void 0:(0,C.default)(!1),null!=o&&(e+=encodeURIComponent(o))):e+=E;return e.replace(/\/+/g,"/")}M.__esModule=!0,M.compilePattern=i,M.matchPattern=T,M.getParamNames=E,M.getParams=N,M.formatPattern=o;var n=t(8),C=I(n),c=Object.create(null)},function(A,M){"use strict";M.__esModule=!0;var t="PUSH";M.PUSH=t;var I="REPLACE";M.REPLACE=I;var g="POP";M.POP=g,M.default={PUSH:t,REPLACE:I,POP:g}},function(A,M,t){"use strict";function I(A,M){return(A&M)===M}var g=t(2),e={MUST_USE_ATTRIBUTE:1,MUST_USE_PROPERTY:2,HAS_SIDE_EFFECTS:4,HAS_BOOLEAN_VALUE:8,HAS_NUMERIC_VALUE:16,HAS_POSITIVE_NUMERIC_VALUE:48,HAS_OVERLOADED_BOOLEAN_VALUE:64,injectDOMPropertyConfig:function(A){var M=e,t=A.Properties||{},i=A.DOMAttributeNamespaces||{},E=A.DOMAttributeNames||{},N=A.DOMPropertyNames||{},o=A.DOMMutationMethods||{};A.isCustomAttribute&&T._isCustomAttributeFunctions.push(A.isCustomAttribute);for(var n in t){T.properties.hasOwnProperty(n)?g(!1):void 0;var C=n.toLowerCase(),c=t[n],D={attributeName:C,attributeNamespace:null,propertyName:n,mutationMethod:null,mustUseAttribute:I(c,M.MUST_USE_ATTRIBUTE),mustUseProperty:I(c,M.MUST_USE_PROPERTY),hasSideEffects:I(c,M.HAS_SIDE_EFFECTS),hasBooleanValue:I(c,M.HAS_BOOLEAN_VALUE),hasNumericValue:I(c,M.HAS_NUMERIC_VALUE),hasPositiveNumericValue:I(c,M.HAS_POSITIVE_NUMERIC_VALUE),hasOverloadedBooleanValue:I(c,M.HAS_OVERLOADED_BOOLEAN_VALUE)};if(D.mustUseAttribute&&D.mustUseProperty?g(!1):void 0,!D.mustUseProperty&&D.hasSideEffects?g(!1):void 0,D.hasBooleanValue+D.hasNumericValue+D.hasOverloadedBooleanValue<=1?void 0:g(!1),E.hasOwnProperty(n)){var a=E[n];D.attributeName=a}i.hasOwnProperty(n)&&(D.attributeNamespace=i[n]),N.hasOwnProperty(n)&&(D.propertyName=N[n]),o.hasOwnProperty(n)&&(D.mutationMethod=o[n]),T.properties[n]=D}}},i={},T={ID_ATTRIBUTE_NAME:"data-reactid",properties:{},getPossibleStandardName:null,_isCustomAttributeFunctions:[],isCustomAttribute:function(A){for(var M=0;M1){var M=A.indexOf(c,1);return M>-1?A.substr(0,M):A}return null},traverseEnterLeave:function(A,M,t,I,g){var e=N(A,M);e!==A&&o(A,e,t,I,!1,!0),e!==M&&o(e,M,t,g,!0,!1)},traverseTwoPhase:function(A,M,t){A&&(o("",A,M,t,!0,!1),o(A,"",M,t,!1,!0))},traverseTwoPhaseSkipTarget:function(A,M,t){A&&(o("",A,M,t,!0,!0),o(A,"",M,t,!0,!0))},traverseAncestors:function(A,M,t){o("",A,M,t,!0,!1)},getFirstCommonAncestorID:N,_getNextDescendantID:E,isAncestorIDOf:i,SEPARATOR:c};A.exports=B},function(A,M){var t=A.exports={version:"1.2.6"};"number"==typeof __e&&(__e=t)},function(A,M,t){"use strict";var I={};A.exports=I},function(A,M,t){"use strict";function I(A,M,t){var I=0;return n.default.Children.map(A,function(A){if(n.default.isValidElement(A)){var g=I;return I++,M.call(t,A,g)}return A})}function g(A,M,t){var I=0;return n.default.Children.forEach(A,function(A){n.default.isValidElement(A)&&(M.call(t,A,I),I++)})}function e(A){var M=0;return n.default.Children.forEach(A,function(A){n.default.isValidElement(A)&&M++}),M}function i(A){var M=!1;return n.default.Children.forEach(A,function(A){!M&&n.default.isValidElement(A)&&(M=!0)}),M}function T(A,M){var t=void 0;return g(A,function(I,g){!t&&M(I,g,A)&&(t=I)}),t}function E(A,M,t){var I=0,g=[];return n.default.Children.forEach(A,function(A){n.default.isValidElement(A)&&(M.call(t,A,I)&&g.push(A),I++)}),g}var N=t(6).default;M.__esModule=!0;var o=t(1),n=N(o);M.default={map:I,forEach:g,numberOf:e,find:T,findValidComponents:E,hasValidComponent:i},A.exports=M.default},function(A,M,t){"use strict";function I(A){return A&&A.__esModule?A:{default:A}}Object.defineProperty(M,"__esModule",{value:!0}),M.default=function(A){return(0,T.default)(e.default.findDOMNode(A))};var g=t(16),e=I(g),i=t(39),T=I(i);A.exports=M.default},function(A,M,t){"use strict";var I=t(204),g=t(437),e=t(217),i=t(226),T=t(227),E=t(2),N=(t(3),{}),o=null,n=function(A,M){A&&(g.executeDispatchesInOrder(A,M),A.isPersistent()||A.constructor.release(A))},C=function(A){return n(A,!0)},c=function(A){return n(A,!1)},D=null,a={injection:{injectMount:g.injection.injectMount,injectInstanceHandle:function(A){D=A},getInstanceHandle:function(){return D},injectEventPluginOrder:I.injectEventPluginOrder,injectEventPluginsByName:I.injectEventPluginsByName},eventNameDispatchConfigs:I.eventNameDispatchConfigs,registrationNameModules:I.registrationNameModules,putListener:function(A,M,t){"function"!=typeof t?E(!1):void 0;var g=N[M]||(N[M]={});g[A]=t;var e=I.registrationNameModules[M];e&&e.didPutListener&&e.didPutListener(A,M,t)},getListener:function(A,M){var t=N[M];return t&&t[A]},deleteListener:function(A,M){var t=I.registrationNameModules[M];t&&t.willDeleteListener&&t.willDeleteListener(A,M);var g=N[M];g&&delete g[A]},deleteAllListeners:function(A){for(var M in N)if(N[M][A]){var t=I.registrationNameModules[M];t&&t.willDeleteListener&&t.willDeleteListener(A,M),delete N[M][A]}},extractEvents:function(A,M,t,g,e){for(var T,E=I.plugins,N=0;N1?I-1:0),e=1;e":">","<":"<",'"':""","'":"'"},e=/[&><"']/g;A.exports=I},function(A,M,t){"use strict";var I=t(10),g=/^[ \r\n\t\f]/,e=/<(!--|link|noscript|meta|script|style)[ \r\n\t\f\/>]/,i=function(A,M){A.innerHTML=M};if("undefined"!=typeof MSApp&&MSApp.execUnsafeLocalFunction&&(i=function(A,M){MSApp.execUnsafeLocalFunction(function(){A.innerHTML=M})}),I.canUseDOM){var T=document.createElement("div");T.innerHTML=" ",""===T.innerHTML&&(i=function(A,M){if(A.parentNode&&A.parentNode.replaceChild(A,A),g.test(M)||"<"===M[0]&&e.test(M)){A.innerHTML=String.fromCharCode(65279)+M;var t=A.firstChild;1===t.data.length?A.removeChild(t):t.deleteData(0,1)}else A.innerHTML=M})}A.exports=i},function(A,M,t){"use strict";function I(A){return A&&A.__esModule?A:{default:A}}Object.defineProperty(M,"__esModule",{value:!0});var g=t(1),e=I(g),i=function(A){var M=A.label,t=A.id,I=A.name,g=A.value,i=A.onChange,T=A.type,E=A.spellCheck,N=A.required,o=A.readonly,n=A.autoComplete,C=A.align,c=A.className,D=e.default.createElement("input",{id:t,name:I,value:g,onChange:i,className:"ig-text",type:T,spellCheck:E,required:N,autoComplete:n});return o&&(D=e.default.createElement("input",{id:t,name:I,value:g,onChange:i,className:"ig-text",type:T,spellCheck:E,required:N,autoComplete:n,disabled:!0})),e.default.createElement("div",{className:"input-group "+C+" "+c},D,e.default.createElement("i",{className:"ig-helpers"}),e.default.createElement("label",{className:"ig-label"},M))};M.default=i},function(A,M,t){A.exports={default:t(259),__esModule:!0}},function(A,M,t){var I=t(265),g=t(49),e=t(134),i="prototype",T=function(A,M,t){var E,N,o,n=A&T.F,C=A&T.G,c=A&T.S,D=A&T.P,a=A&T.B,B=A&T.W,Q=C?g:g[M]||(g[M]={}),r=C?I:c?I[M]:(I[M]||{})[i];C&&(t=M);for(E in t)N=!n&&r&&E in r,N&&E in Q||(o=N?r[E]:t[E],Q[E]=C&&"function"!=typeof r[E]?t[E]:a&&N?e(o,I):B&&r[E]==o?function(A){var M=function(M){return this instanceof A?new A(M):A(M)};return M[i]=A[i],M}(o):D&&"function"==typeof o?e(Function.call,o):o,D&&((Q[i]||(Q[i]={}))[E]=o))};T.F=1,T.G=2,T.S=4,T.P=8,T.B=16,T.W=32,A.exports=T},function(A,M){var t=Object;A.exports={create:t.create,getProto:t.getPrototypeOf,isEnum:{}.propertyIsEnumerable,getDesc:t.getOwnPropertyDescriptor,setDesc:t.defineProperty,setDescs:t.defineProperties,getKeys:t.keys,getNames:t.getOwnPropertyNames,getSymbols:t.getOwnPropertySymbols,each:[].forEach}},function(A,M,t){"use strict";var I=t(32),g=function(){};I&&(g=function(){return document.addEventListener?function(A,M,t,I){return A.addEventListener(M,t,I||!1)}:document.attachEvent?function(A,M,t){return A.attachEvent("on"+M,t)}:void 0}()),A.exports=g},function(A,M,t){"use strict";var I=t(145),g=t(295),e=t(290),i=t(291),T=Object.prototype.hasOwnProperty;A.exports=function(A,M,t){var E="",N=M;if("string"==typeof M){if(void 0===t)return A.style[I(M)]||e(A).getPropertyValue(g(M));(N={})[M]=t}for(var o in N)T.call(N,o)&&(N[o]||0===N[o]?E+=g(o)+":"+N[o]+";":i(A,g(o)));A.style.cssText+=";"+E}},function(A,M,t){(function(){var t=this,I=t.humanize,g={};"undefined"!=typeof A&&A.exports&&(M=A.exports=g),M.humanize=g,g.noConflict=function(){return t.humanize=I,this},g.pad=function(A,M,t,I){if(A+="",t?t.length>1&&(t=t.charAt(0)):t=" ",I=void 0===I?"left":"right","right"===I)for(;A.length4&&A<21?"th":{1:"st",2:"nd",3:"rd"}[A%10]||"th"},w:function(){return t.getDay()},z:function(){return(o.L()?i[o.n()]:e[o.n()])+o.j()-1},W:function(){var A=o.z()-o.N()+1.5;return g.pad(1+Math.floor(Math.abs(A)/7)+(A%7>3.5?1:0),2,"0")},F:function(){return N[t.getMonth()]},m:function(){return g.pad(o.n(),2,"0")},M:function(){return o.F().slice(0,3)},n:function(){return t.getMonth()+1},t:function(){return new Date(o.Y(),o.n(),0).getDate()},L:function(){return 1===new Date(o.Y(),1,29).getMonth()?1:0},o:function(){var A=o.n(),M=o.W();return o.Y()+(12===A&&M<9?-1:1===A&&M>9)},Y:function(){return t.getFullYear()},y:function(){return String(o.Y()).slice(-2)},a:function(){return t.getHours()>11?"pm":"am"},A:function(){return o.a().toUpperCase()},B:function(){var A=t.getTime()/1e3,M=A%86400+3600;M<0&&(M+=86400);var I=M/86.4%1e3;return A<0?Math.ceil(I):Math.floor(I)},g:function(){return o.G()%12||12},G:function(){return t.getHours()},h:function(){return g.pad(o.g(),2,"0")},H:function(){return g.pad(o.G(),2,"0")},i:function(){return g.pad(t.getMinutes(),2,"0")},s:function(){return g.pad(t.getSeconds(),2,"0")},u:function(){return g.pad(1e3*t.getMilliseconds(),6,"0")},O:function(){var A=t.getTimezoneOffset(),M=Math.abs(A);return(A>0?"-":"+")+g.pad(100*Math.floor(M/60)+M%60,4,"0")},P:function(){var A=o.O();return A.substr(0,3)+":"+A.substr(3,2)},Z:function(){return 60*-t.getTimezoneOffset()},c:function(){return"Y-m-d\\TH:i:sP".replace(I,T)},r:function(){return"D, d M Y H:i:s O".replace(I,T)},U:function(){return t.getTime()/1e3||0}};return A.replace(I,T)},g.numberFormat=function(A,M,t,I){M=isNaN(M)?2:Math.abs(M),t=void 0===t?".":t,I=void 0===I?",":I;var g=A<0?"-":"";A=Math.abs(+A||0);var e=parseInt(A.toFixed(M),10)+"",i=e.length>3?e.length%3:0;return g+(i?e.substr(0,i)+I:"")+e.substr(i).replace(/(\d{3})(?=\d)/g,"$1"+I)+(M?t+Math.abs(A-e).toFixed(M).slice(2):"")},g.naturalDay=function(A,M){A=void 0===A?g.time():A,M=void 0===M?"Y-m-d":M;var t=86400,I=new Date,e=new Date(I.getFullYear(),I.getMonth(),I.getDate()).getTime()/1e3;return A=e-t?"yesterday":A>=e&&A=e+t&&A-2)return(t>=0?"just ":"")+"now";if(t<60&&t>-60)return t>=0?Math.floor(t)+" seconds ago":"in "+Math.floor(-t)+" seconds";if(t<120&&t>-120)return t>=0?"about a minute ago":"in about a minute";if(t<3600&&t>-3600)return t>=0?Math.floor(t/60)+" minutes ago":"in "+Math.floor(-t/60)+" minutes";if(t<7200&&t>-7200)return t>=0?"about an hour ago":"in about an hour";if(t<86400&&t>-86400)return t>=0?Math.floor(t/3600)+" hours ago":"in "+Math.floor(-t/3600)+" hours";var I=172800;if(t-I)return t>=0?"1 day ago":"in 1 day";var e=2505600;if(t-e)return t>=0?Math.floor(t/86400)+" days ago":"in "+Math.floor(-t/86400)+" days";var i=5184e3;if(t-i)return t>=0?"about a month ago":"in about a month";var T=parseInt(g.date("Y",M),10),E=parseInt(g.date("Y",A),10),N=12*T+parseInt(g.date("n",M),10),o=12*E+parseInt(g.date("n",A),10),n=N-o;if(n<12&&n>-12)return n>=0?n+" months ago":"in "+-n+" months";var C=T-E;return C<2&&C>-2?C>=0?"a year ago":"in a year":C>=0?C+" years ago":"in "+-C+" years"},g.ordinal=function(A){A=parseInt(A,10),A=isNaN(A)?0:A;var M=A<0?"-":"";A=Math.abs(A);var t=A%100;return M+A+(t>4&&t<21?"th":{1:"st",2:"nd",3:"rd"}[A%10]||"th")},g.filesize=function(A,M,t,I,e,i){return M=void 0===M?1024:M,A<=0?"0 bytes":(A

    "),A=A.replace(/\n/g,"
    "),"

    "+A+"

    "},g.nl2br=function(A){return A.replace(/(\r\n|\n|\r)/g,"
    ")},g.truncatechars=function(A,M){return A.length<=M?A:A.substr(0,M)+"…"},g.truncatewords=function(A,M){var t=A.split(" ");return t.length0,Q=C.enumErrorProps&&(A===u||A instanceof Error),r=C.enumPrototypes&&T(A);++IM.documentElement.clientHeight;return{modalStyles:{paddingRight:I&&!g?Q.default():void 0,paddingLeft:!I&&g?Q.default():void 0}}}});X.Body=z.default,X.Header=U.default,X.Title=m.default,X.Footer=f.default,X.Dialog=h.default,X.TRANSITION_DURATION=300,X.BACKDROP_TRANSITION_DURATION=150,M.default=c.bsSizes([a.Sizes.LARGE,a.Sizes.SMALL],c.bsClass("modal",X)),A.exports=M.default},function(A,M,t){"use strict";var I=t(15).default,g=t(14).default,e=t(38).default,i=t(7).default,T=t(6).default;M.__esModule=!0;var E=t(1),N=T(E),o=t(5),n=T(o),C=t(11),c=T(C),D=t(44),a=T(D),B=function(A){function M(){g(this,M),A.apply(this,arguments)}return I(M,A),M.prototype.render=function(){var A=this.props,M=A["aria-label"],t=e(A,["aria-label"]),I=a.default(this.context.$bs_onModalHide,this.props.onHide);return N.default.createElement("div",i({},t,{className:n.default(this.props.className,c.default.prefix(this.props,"header"))}),this.props.closeButton&&N.default.createElement("button",{type:"button",className:"close","aria-label":M,onClick:I},N.default.createElement("span",{"aria-hidden":"true" -},"×")),this.props.children)},M}(N.default.Component);B.propTypes={"aria-label":N.default.PropTypes.string,bsClass:N.default.PropTypes.string,closeButton:N.default.PropTypes.bool,onHide:N.default.PropTypes.func},B.contextTypes={$bs_onModalHide:N.default.PropTypes.func},B.defaultProps={"aria-label":"Close",closeButton:!1},M.default=C.bsClass("modal",B),A.exports=M.default},function(A,M,t){"use strict";function I(A,M){return Array.isArray(M)?M.indexOf(A)>=0:A===M}var g=t(7).default,e=t(78).default,i=t(6).default;M.__esModule=!0;var T=t(40),E=i(T),N=t(170),o=i(N),n=t(1),C=i(n),c=t(16),D=i(c),a=t(127),B=(i(a),t(370)),Q=i(B),r=t(44),s=i(r),x=C.default.createClass({displayName:"OverlayTrigger",propTypes:g({},Q.default.propTypes,{trigger:C.default.PropTypes.oneOfType([C.default.PropTypes.oneOf(["click","hover","focus"]),C.default.PropTypes.arrayOf(C.default.PropTypes.oneOf(["click","hover","focus"]))]),delay:C.default.PropTypes.number,delayShow:C.default.PropTypes.number,delayHide:C.default.PropTypes.number,defaultOverlayShown:C.default.PropTypes.bool,overlay:C.default.PropTypes.node.isRequired,onBlur:C.default.PropTypes.func,onClick:C.default.PropTypes.func,onFocus:C.default.PropTypes.func,onMouseEnter:C.default.PropTypes.func,onMouseLeave:C.default.PropTypes.func,target:function(){},onHide:function(){},show:function(){}}),getDefaultProps:function(){return{defaultOverlayShown:!1,trigger:["hover","focus"]}},getInitialState:function(){return{isOverlayShown:this.props.defaultOverlayShown}},show:function(){this.setState({isOverlayShown:!0})},hide:function(){this.setState({isOverlayShown:!1})},toggle:function(){this.state.isOverlayShown?this.hide():this.show()},componentWillMount:function(){this.handleMouseOver=this.handleMouseOverOut.bind(null,this.handleDelayedShow),this.handleMouseOut=this.handleMouseOverOut.bind(null,this.handleDelayedHide)},componentDidMount:function(){this._mountNode=document.createElement("div"),this.renderOverlay()},renderOverlay:function(){D.default.unstable_renderSubtreeIntoContainer(this,this._overlay,this._mountNode)},componentWillUnmount:function(){D.default.unmountComponentAtNode(this._mountNode),this._mountNode=null,clearTimeout(this._hoverShowDelay),clearTimeout(this._hoverHideDelay)},componentDidUpdate:function(){this._mountNode&&this.renderOverlay()},getOverlayTarget:function(){return D.default.findDOMNode(this)},getOverlay:function(){var A=g({},o.default(this.props,e(Q.default.propTypes)),{show:this.state.isOverlayShown,onHide:this.hide,target:this.getOverlayTarget,onExit:this.props.onExit,onExiting:this.props.onExiting,onExited:this.props.onExited,onEnter:this.props.onEnter,onEntering:this.props.onEntering,onEntered:this.props.onEntered}),M=n.cloneElement(this.props.overlay,{placement:A.placement,container:A.container});return C.default.createElement(Q.default,A,M)},render:function(){var A=C.default.Children.only(this.props.children),M=A.props,t={"aria-describedby":this.props.overlay.props.id};return this._overlay=this.getOverlay(),t.onClick=s.default(M.onClick,this.props.onClick),I("click",this.props.trigger)&&(t.onClick=s.default(this.toggle,t.onClick)),I("hover",this.props.trigger)&&(t.onMouseOver=s.default(this.handleMouseOver,this.props.onMouseOver,M.onMouseOver),t.onMouseOut=s.default(this.handleMouseOut,this.props.onMouseOut,M.onMouseOut)),I("focus",this.props.trigger)&&(t.onFocus=s.default(this.handleDelayedShow,this.props.onFocus,M.onFocus),t.onBlur=s.default(this.handleDelayedHide,this.props.onBlur,M.onBlur)),n.cloneElement(A,t)},handleDelayedShow:function(){var A=this;if(null!=this._hoverHideDelay)return clearTimeout(this._hoverHideDelay),void(this._hoverHideDelay=null);if(!this.state.isOverlayShown&&null==this._hoverShowDelay){var M=null!=this.props.delayShow?this.props.delayShow:this.props.delay;return M?void(this._hoverShowDelay=setTimeout(function(){A._hoverShowDelay=null,A.show()},M)):void this.show()}},handleDelayedHide:function(){var A=this;if(null!=this._hoverShowDelay)return clearTimeout(this._hoverShowDelay),void(this._hoverShowDelay=null);if(this.state.isOverlayShown&&null==this._hoverHideDelay){var M=null!=this.props.delayHide?this.props.delayHide:this.props.delay;return M?void(this._hoverHideDelay=setTimeout(function(){A._hoverHideDelay=null,A.hide()},M)):void this.hide()}},handleMouseOverOut:function(A,M){var t=M.currentTarget,I=M.relatedTarget||M.nativeEvent.toElement;I&&(I===t||E.default(t,I))||A(M)}});M.default=x,A.exports=M.default},function(A,M,t){"use strict";var I=t(7).default,g=t(6).default;M.__esModule=!0;var e=t(1),i=g(e),T=t(5),E=g(T),N=t(11),o=g(N),n=t(183),C=g(n),c=i.default.createClass({displayName:"Tooltip",propTypes:{id:C.default(i.default.PropTypes.oneOfType([i.default.PropTypes.string,i.default.PropTypes.number])),placement:i.default.PropTypes.oneOf(["top","right","bottom","left"]),positionLeft:i.default.PropTypes.number,positionTop:i.default.PropTypes.number,arrowOffsetLeft:i.default.PropTypes.oneOfType([i.default.PropTypes.number,i.default.PropTypes.string]),arrowOffsetTop:i.default.PropTypes.oneOfType([i.default.PropTypes.number,i.default.PropTypes.string]),title:i.default.PropTypes.node},getDefaultProps:function(){return{bsClass:"tooltip",placement:"right"}},render:function(){var A,M=(A={},A[o.default.prefix(this.props)]=!0,A[this.props.placement]=!0,A),t=I({left:this.props.positionLeft,top:this.props.positionTop},this.props.style),g={left:this.props.arrowOffsetLeft,top:this.props.arrowOffsetTop};return i.default.createElement("div",I({role:"tooltip"},this.props,{className:E.default(this.props.className,M),style:t}),i.default.createElement("div",{className:o.default.prefix(this.props,"arrow"),style:g}),i.default.createElement("div",{className:o.default.prefix(this.props,"inner")},this.props.children))}});M.default=c,A.exports=M.default},function(A,M,t){"use strict";function I(A){return A&&A.__esModule?A:{default:A}}function g(A,M){return A="function"==typeof A?A():A,i.default.findDOMNode(A)||M}Object.defineProperty(M,"__esModule",{value:!0}),M.default=g;var e=t(16),i=I(e);A.exports=M.default},function(A,M,t){"use strict";function I(A){return A&&A.__esModule?A:{default:A}}function g(A,M,t,I,g){var i=A[M],E="undefined"==typeof i?"undefined":e(i);return T.default.isValidElement(i)?new Error("Invalid "+I+" ` + "`" + `"+g+"` + "`" + ` of type ReactElement "+("supplied to ` + "`" + `"+t+"` + "`" + `, expected a ReactComponent or a ")+"DOMElement. You can usually obtain a ReactComponent or DOMElement from a ReactElement by attaching a ref to it."):"object"===E&&"function"==typeof i.render||1===i.nodeType?null:new Error("Invalid "+I+" ` + "`" + `"+g+"` + "`" + ` of value ` + "`" + `"+i+"` + "`" + ` "+("supplied to ` + "`" + `"+t+"` + "`" + `, expected a ReactComponent or a ")+"DOMElement.")}M.__esModule=!0;var e="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(A){return typeof A}:function(A){return A&&"function"==typeof Symbol&&A.constructor===Symbol?"symbol":typeof A},i=t(1),T=I(i),E=t(182),N=I(E);M.default=(0,N.default)(g)},function(A,M,t){"use strict";function I(){function A(A,M,I){for(var g=0;g>",null!=t[I]?A(t,I,g):M?new Error("Required prop '"+I+"' was not specified in '"+g+"'."):void 0}var t=M.bind(null,!1);return t.isRequired=M.bind(null,!0),t}M.__esModule=!0,M.errMsg=t,M.createChainableTypeChecker=I},function(A,M){"use strict";function t(A,M,t){function I(){return i=!0,T?void(N=[].concat(Array.prototype.slice.call(arguments))):void t.apply(this,arguments)}function g(){if(!i&&(E=!0,!T)){for(T=!0;!i&&e=A&&E&&(i=!0,t()))}}var e=0,i=!1,T=!1,E=!1,N=void 0;g()}function I(A,M,t){function I(A,M,I){i||(M?(i=!0,t(M)):(e[A]=I,i=++T===g,i&&t(null,e)))}var g=A.length,e=[];if(0===g)return t(null,e);var i=!1,T=0;A.forEach(function(A,t){M(A,t,function(A,M){I(t,A,M)})})}M.__esModule=!0,M.loopAsync=t,M.mapAsync=I},function(A,M,t){"use strict";function I(A){if(A&&A.__esModule)return A;var M={};if(null!=A)for(var t in A)Object.prototype.hasOwnProperty.call(A,t)&&(M[t]=A[t]);return M.default=A,M}function g(A){return A&&A.__esModule?A:{default:A}}M.__esModule=!0,M.router=M.routes=M.route=M.components=M.component=M.location=M.history=M.falsy=M.locationShape=M.routerShape=void 0;var e=t(1),i=t(66),T=(g(i),t(34)),E=I(T),N=t(9),o=(g(N),e.PropTypes.func),n=e.PropTypes.object,C=e.PropTypes.shape,c=e.PropTypes.string,D=M.routerShape=C({push:o.isRequired,replace:o.isRequired,go:o.isRequired,goBack:o.isRequired,goForward:o.isRequired,setRouteLeaveHook:o.isRequired,isActive:o.isRequired}),a=M.locationShape=C({pathname:c.isRequired,search:c.isRequired,state:n,action:c.isRequired,key:c}),B=M.falsy=E.falsy,Q=M.history=E.history,r=M.location=a,s=M.component=E.component,x=M.components=E.components,j=M.route=E.route,y=(M.routes=E.routes,M.router=D),u={falsy:B,history:Q,location:r,component:s,components:x,route:j,router:y};M.default=u},function(A,M,t){"use strict";function I(A){return A&&A.__esModule?A:{default:A}}function g(A){for(var M in A)if(Object.prototype.hasOwnProperty.call(A,M))return!0;return!1}function e(A,M){function t(M){var t=!(arguments.length<=1||void 0===arguments[1])&&arguments[1],I=arguments.length<=2||void 0===arguments[2]?null:arguments[2],g=void 0;return t&&t!==!0||null!==I?(M={pathname:M,query:t},g=I||!1):(M=A.createLocation(M),g=t),(0,C.default)(M,g,s.location,s.routes,s.params)}function I(A,t){x&&x.location===A?e(x,t):(0,B.default)(M,A,function(M,I){M?t(M):I?e(i({},I,{location:A}),t):t()})}function e(A,M){function t(t,g){return t||g?I(t,g):void(0,D.default)(A,function(t,I){t?M(t):M(null,null,s=i({},A,{components:I}))})}function I(A,t){A?M(A):M(null,t)}var g=(0,N.default)(s,A),e=g.leaveRoutes,T=g.changeRoutes,E=g.enterRoutes;(0,o.runLeaveHooks)(e,s),e.filter(function(A){return E.indexOf(A)===-1}).forEach(a),(0,o.runChangeHooks)(T,s,A,function(M,g){return M||g?I(M,g):void(0,o.runEnterHooks)(E,A,t)})}function T(A){var M=arguments.length<=1||void 0===arguments[1]||arguments[1];return A.__id__||M&&(A.__id__=j++)}function E(A){return A.reduce(function(A,M){return A.push.apply(A,y[T(M)]),A},[])}function n(A,t){(0,B.default)(M,A,function(M,I){if(null==I)return void t();x=i({},I,{location:A});for(var g=E((0,N.default)(s,x).leaveRoutes),e=void 0,T=0,o=g.length;null==e&&T=32||13===M?M:0}A.exports=t},function(A,M){"use strict";function t(A){var M=this,t=M.nativeEvent;if(t.getModifierState)return t.getModifierState(A);var I=g[A];return!!I&&!!t[I]}function I(A){return t}var g={Alt:"altKey",Control:"ctrlKey",Meta:"metaKey",Shift:"shiftKey"};A.exports=I},function(A,M){"use strict";function t(A){var M=A.target||A.srcElement||window;return 3===M.nodeType?M.parentNode:M}A.exports=t},function(A,M){"use strict";function t(A){var M=A&&(I&&A[I]||A[g]);if("function"==typeof M)return M}var I="function"==typeof Symbol&&Symbol.iterator,g="@@iterator";A.exports=t},function(A,M,t){"use strict";function I(A){return"function"==typeof A&&"undefined"!=typeof A.prototype&&"function"==typeof A.prototype.mountComponent&&"function"==typeof A.prototype.receiveComponent}function g(A){var M;if(null===A||A===!1)M=new i(g);else if("object"==typeof A){var t=A;!t||"function"!=typeof t.type&&"string"!=typeof t.type?N(!1):void 0,M="string"==typeof t.type?T.createInternalComponent(t):I(t.type)?new t.type(t):new o}else"string"==typeof A||"number"==typeof A?M=T.createInstanceForText(A):N(!1);return M.construct(A),M._mountIndex=0,M._mountImage=null,M}var e=t(443),i=t(215),T=t(221),E=t(4),N=t(2),o=(t(3),function(){});E(o.prototype,e.Mixin,{_instantiateReactComponent:g}),A.exports=g},function(A,M,t){"use strict";/** +!function(){"use strict";function t(){for(var A=[],M=0;M2?t-2:0),g=2;g1){for(var c=Array(C),D=0;D1){for(var a=Array(D),B=0;B3&&void 0!==arguments[3]?arguments[3]:{},N=Boolean(A),C=A||w,D=void 0;D="function"==typeof M?M:M?(0,Q.default)(M):l;var B=t||L,r=I.pure,s=void 0===r||r,x=I.withRef,y=void 0!==x&&x,h=s&&B!==L,S=d++;return function(A){function M(A,M,t){var I=B(A,M,t);return I}var t="Connect("+T(A)+")",I=function(I){function T(A,M){g(this,T);var i=e(this,I.call(this,A,M));i.version=S,i.store=A.store||M.store,(0,u.default)(i.store,'Could not find "store" in either the context or '+('props of "'+t+'". ')+"Either wrap the root component in a , "+('or explicitly pass "store" as a prop to "'+t+'".'));var E=i.store.getState();return i.state={storeState:E},i.clearCache(),i}return i(T,I),T.prototype.shouldComponentUpdate=function(){return!s||this.haveOwnPropsChanged||this.hasStoreStateChanged},T.prototype.computeStateProps=function(A,M){if(!this.finalMapStateToProps)return this.configureFinalMapState(A,M);var t=A.getState(),I=this.doStatePropsDependOnOwnProps?this.finalMapStateToProps(t,M):this.finalMapStateToProps(t);return I},T.prototype.configureFinalMapState=function(A,M){var t=C(A.getState(),M),I="function"==typeof t;return this.finalMapStateToProps=I?t:C,this.doStatePropsDependOnOwnProps=1!==this.finalMapStateToProps.length,I?this.computeStateProps(A,M):t},T.prototype.computeDispatchProps=function(A,M){if(!this.finalMapDispatchToProps)return this.configureFinalMapDispatch(A,M);var t=A.dispatch,I=this.doDispatchPropsDependOnOwnProps?this.finalMapDispatchToProps(t,M):this.finalMapDispatchToProps(t);return I},T.prototype.configureFinalMapDispatch=function(A,M){var t=D(A.dispatch,M),I="function"==typeof t;return this.finalMapDispatchToProps=I?t:D,this.doDispatchPropsDependOnOwnProps=1!==this.finalMapDispatchToProps.length,I?this.computeDispatchProps(A,M):t},T.prototype.updateStatePropsIfNeeded=function(){var A=this.computeStateProps(this.store,this.props);return(!this.stateProps||!(0,a.default)(A,this.stateProps))&&(this.stateProps=A,!0)},T.prototype.updateDispatchPropsIfNeeded=function(){var A=this.computeDispatchProps(this.store,this.props);return(!this.dispatchProps||!(0,a.default)(A,this.dispatchProps))&&(this.dispatchProps=A,!0)},T.prototype.updateMergedPropsIfNeeded=function(){var A=M(this.stateProps,this.dispatchProps,this.props);return!(this.mergedProps&&h&&(0,a.default)(A,this.mergedProps))&&(this.mergedProps=A,!0)},T.prototype.isSubscribed=function(){return"function"==typeof this.unsubscribe},T.prototype.trySubscribe=function(){N&&!this.unsubscribe&&(this.unsubscribe=this.store.subscribe(this.handleChange.bind(this)),this.handleChange())},T.prototype.tryUnsubscribe=function(){this.unsubscribe&&(this.unsubscribe(),this.unsubscribe=null)},T.prototype.componentDidMount=function(){this.trySubscribe()},T.prototype.componentWillReceiveProps=function(A){s&&(0,a.default)(A,this.props)||(this.haveOwnPropsChanged=!0)},T.prototype.componentWillUnmount=function(){this.tryUnsubscribe(),this.clearCache()},T.prototype.clearCache=function(){this.dispatchProps=null,this.stateProps=null,this.mergedProps=null,this.haveOwnPropsChanged=!0,this.hasStoreStateChanged=!0,this.haveStatePropsBeenPrecalculated=!1,this.statePropsPrecalculationError=null,this.renderedElement=null,this.finalMapDispatchToProps=null,this.finalMapStateToProps=null},T.prototype.handleChange=function(){if(this.unsubscribe){var A=this.store.getState(),M=this.state.storeState;if(!s||M!==A){if(s&&!this.doStatePropsDependOnOwnProps){var t=E(this.updateStatePropsIfNeeded,this);if(!t)return;t===Y&&(this.statePropsPrecalculationError=Y.value),this.haveStatePropsBeenPrecalculated=!0}this.hasStoreStateChanged=!0,this.setState({storeState:A})}}},T.prototype.getWrappedInstance=function(){return(0,u.default)(y,"To access the wrapped instance, you need to specify { withRef: true } as the fourth argument of the connect() call."),this.refs.wrappedInstance},T.prototype.render=function(){var M=this.haveOwnPropsChanged,t=this.hasStoreStateChanged,I=this.haveStatePropsBeenPrecalculated,g=this.statePropsPrecalculationError,e=this.renderedElement;if(this.haveOwnPropsChanged=!1,this.hasStoreStateChanged=!1,this.haveStatePropsBeenPrecalculated=!1,this.statePropsPrecalculationError=null,g)throw g;var i=!0,T=!0;s&&e&&(i=t||M&&this.doStatePropsDependOnOwnProps,T=M&&this.doDispatchPropsDependOnOwnProps);var E=!1,N=!1;I?E=!0:i&&(E=this.updateStatePropsIfNeeded()),T&&(N=this.updateDispatchPropsIfNeeded());var C=!0;return C=!!(E||N||M)&&this.updateMergedPropsIfNeeded(),!C&&e?e:(y?this.renderedElement=(0,n.createElement)(A,o({},this.mergedProps,{ref:"wrappedInstance"})):this.renderedElement=(0,n.createElement)(A,this.mergedProps),this.renderedElement)},T}(n.Component);return I.displayName=t,I.WrappedComponent=A,I.contextTypes={store:c.default},I.propTypes={store:c.default},(0,j.default)(I,A)}}M.__esModule=!0;var o=Object.assign||function(A){for(var M=1;M should not have a "'+M+'" prop')}M.__esModule=!0,M.routes=M.route=M.components=M.component=M.history=void 0,M.falsy=I;var g=t(1),e=g.PropTypes.func,i=g.PropTypes.object,T=g.PropTypes.arrayOf,E=g.PropTypes.oneOfType,N=g.PropTypes.element,o=g.PropTypes.shape,n=g.PropTypes.string,C=(M.history=o({listen:e.isRequired,push:e.isRequired,replace:e.isRequired,go:e.isRequired,goBack:e.isRequired,goForward:e.isRequired}),M.component=E([e,n])),c=(M.components=E([C,i]),M.route=E([i,N]));M.routes=E([c,T(c)])},function(A,M,t){"use strict";function I(A){return A&&A.__esModule?A:{default:A}}function g(A){var M=A.match(/^https?:\/\/[^\/]*/);return null==M?A:A.substring(M[0].length)}function e(A){var M=g(A),t="",I="",e=M.indexOf("#");e!==-1&&(I=M.substring(e),M=M.substring(0,e));var i=M.indexOf("?");return i!==-1&&(t=M.substring(i),M=M.substring(0,i)),""===M&&(M="/"),{pathname:M,search:t,hash:I}}M.__esModule=!0,M.extractPath=g,M.parsePath=e;var i=t(23);I(i)},function(A,M,t){"use strict";function I(){g.attachRefs(this,this._currentElement)}var g=t(458),e={mountComponent:function(A,M,t,g){var e=A.mountComponent(M,t,g);return A._currentElement&&null!=A._currentElement.ref&&t.getReactMountReady().enqueue(I,A),e},unmountComponent:function(A){g.detachRefs(A,A._currentElement),A.unmountComponent()},receiveComponent:function(A,M,t,e){var i=A._currentElement;if(M!==i||e!==A._context){var T=g.shouldUpdateRefs(i,M);T&&g.detachRefs(A,i),A.receiveComponent(M,t,e),T&&A._currentElement&&null!=A._currentElement.ref&&t.getReactMountReady().enqueue(I,A)}},performUpdateIfNecessary:function(A,M){A.performUpdateIfNecessary(M)}};A.exports=e},function(A,M,t){"use strict";function I(A,M,t,I){this.dispatchConfig=A,this.dispatchMarker=M,this.nativeEvent=t;var g=this.constructor.Interface;for(var e in g)if(g.hasOwnProperty(e)){var T=g[e];T?this[e]=T(t):"target"===e?this.target=I:this[e]=t[e]}var E=null!=t.defaultPrevented?t.defaultPrevented:t.returnValue===!1;E?this.isDefaultPrevented=i.thatReturnsTrue:this.isDefaultPrevented=i.thatReturnsFalse,this.isPropagationStopped=i.thatReturnsFalse}var g=t(31),e=t(4),i=t(20),T=(t(3),{type:null,target:null,currentTarget:i.thatReturnsNull,eventPhase:null,bubbles:null,cancelable:null,timeStamp:function(A){return A.timeStamp||Date.now()},defaultPrevented:null,isTrusted:null});e(I.prototype,{preventDefault:function(){this.defaultPrevented=!0;var A=this.nativeEvent;A&&(A.preventDefault?A.preventDefault():A.returnValue=!1,this.isDefaultPrevented=i.thatReturnsTrue)},stopPropagation:function(){var A=this.nativeEvent;A&&(A.stopPropagation?A.stopPropagation():A.cancelBubble=!0,this.isPropagationStopped=i.thatReturnsTrue)},persist:function(){this.isPersistent=i.thatReturnsTrue},isPersistent:i.thatReturnsFalse,destructor:function(){var A=this.constructor.Interface;for(var M in A)this[M]=null;this.dispatchConfig=null,this.dispatchMarker=null,this.nativeEvent=null}}),I.Interface=T,I.augmentClass=function(A,M){var t=this,I=Object.create(t.prototype);e(I,A.prototype),A.prototype=I,A.prototype.constructor=A,A.Interface=e({},t.Interface,M),A.augmentClass=t.augmentClass,g.addPoolingTo(A,g.fourArgumentPooler)},g.addPoolingTo(I,g.fourArgumentPooler),A.exports=I},function(A,M){"use strict";M.default=function(A,M){var t={};for(var I in A)M.indexOf(I)>=0||Object.prototype.hasOwnProperty.call(A,I)&&(t[I]=A[I]);return t},M.__esModule=!0},function(A,M){"use strict";function t(A){return A&&A.ownerDocument||document}M.__esModule=!0,M.default=t,A.exports=M.default},function(A,M,t){"use strict";var I=t(32),g=function(){var A=I&&document.documentElement;return A&&A.contains?function(A,M){return A.contains(M)}:A&&A.compareDocumentPosition?function(A,M){return A===M||!!(16&A.compareDocumentPosition(M))}:function(A,M){if(M)do if(M===A)return!0;while(M=M.parentNode);return!1}}();A.exports=g},function(A,M){function t(A){return"number"==typeof A&&A>-1&&A%1==0&&A<=I}var I=9007199254740991;A.exports=t},function(A,M,t){(function(A){!function(M,t){A.exports=t()}(this,function(){"use strict";function M(){return aI.apply(null,arguments)}function t(A){aI=A}function I(A){return A instanceof Array||"[object Array]"===Object.prototype.toString.call(A)}function g(A){return null!=A&&"[object Object]"===Object.prototype.toString.call(A)}function e(A){var M;for(M in A)return!1;return!0}function i(A){return"number"==typeof A||"[object Number]"===Object.prototype.toString.call(A)}function T(A){return A instanceof Date||"[object Date]"===Object.prototype.toString.call(A)}function E(A,M){var t,I=[];for(t=0;t0)for(t in rI)I=rI[t],g=M[I],B(g)||(A[I]=g);return A}function r(A){Q(this,A),this._d=new Date(null!=A._d?A._d.getTime():NaN),this.isValid()||(this._d=new Date(NaN)),sI===!1&&(sI=!0,M.updateOffset(this),sI=!1)}function s(A){return A instanceof r||null!=A&&null!=A._isAMomentObject}function x(A){return A<0?Math.ceil(A)||0:Math.floor(A)}function j(A){var M=+A,t=0;return 0!==M&&isFinite(M)&&(t=x(M)),t}function y(A,M,t){var I,g=Math.min(A.length,M.length),e=Math.abs(A.length-M.length),i=0;for(I=0;I0?"future":"past"];return L(t)?t(M):t.replace(/%s/i,M)}function F(A,M){var t=A.toLowerCase();SI[t]=SI[t+"s"]=SI[M]=A}function f(A){return"string"==typeof A?SI[A]||SI[A.toLowerCase()]:void 0}function k(A){var M,t,I={};for(t in A)N(A,t)&&(M=f(t),M&&(I[M]=A[t]));return I}function R(A,M){zI[A]=M}function J(A){var M=[];for(var t in A)M.push({unit:t,priority:zI[t]});return M.sort(function(A,M){return A.priority-M.priority}),M}function G(A,t){return function(I){return null!=I?(b(this,A,I),M.updateOffset(this,t),this):H(this,A)}}function H(A,M){return A.isValid()?A._d["get"+(A._isUTC?"UTC":"")+M]():NaN}function b(A,M,t){A.isValid()&&A._d["set"+(A._isUTC?"UTC":"")+M](t)}function v(A){return A=f(A),L(this[A])?this[A]():this}function X(A,M){if("object"==typeof A){A=k(A);for(var t=J(A),I=0;I=0;return(e?t?"+":"":"-")+Math.pow(10,Math.max(0,g)).toString().substr(1)+I}function V(A,M,t,I){var g=I;"string"==typeof I&&(g=function(){return this[I]()}),A&&(mI[A]=g),M&&(mI[M[0]]=function(){return W(g.apply(this,arguments),M[1],M[2])}),t&&(mI[t]=function(){return this.localeData().ordinal(g.apply(this,arguments),A)})}function P(A){return A.match(/\[[\s\S]/)?A.replace(/^\[|\]$/g,""):A.replace(/\\/g,"")}function Z(A){var M,t,I=A.match(pI);for(M=0,t=I.length;M=0&&UI.test(A);)A=A.replace(UI,t),UI.lastIndex=0,I-=1;return A}function _(A,M,t){$I[A]=L(M)?M:function(A,I){return A&&t?t:M}}function $(A,M){return N($I,A)?$I[A](M._strict,M._locale):new RegExp(AA(A))}function AA(A){return MA(A.replace("\\","").replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(A,M,t,I,g){return M||t||I||g}))}function MA(A){return A.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}function tA(A,M){var t,I=M;for("string"==typeof A&&(A=[A]),i(M)&&(I=function(A,t){t[M]=j(A)}),t=0;t=0&&isFinite(T.getFullYear())&&T.setFullYear(A),T}function xA(A){var M=new Date(Date.UTC.apply(null,arguments));return A<100&&A>=0&&isFinite(M.getUTCFullYear())&&M.setUTCFullYear(A),M}function jA(A,M,t){var I=7+M-t,g=(7+xA(A,0,I).getUTCDay()-M)%7;return-g+I-1}function yA(A,M,t,I,g){var e,i,T=(7+t-I)%7,E=jA(A,I,g),N=1+7*(M-1)+T+E;return N<=0?(e=A-1,i=BA(e)+N):N>BA(A)?(e=A+1,i=N-BA(A)):(e=A,i=N),{year:e,dayOfYear:i}}function uA(A,M,t){var I,g,e=jA(A.year(),M,t),i=Math.floor((A.dayOfYear()-e-1)/7)+1;return i<1?(g=A.year()-1,I=i+wA(g,M,t)):i>wA(A.year(),M,t)?(I=i-wA(A.year(),M,t),g=A.year()+1):(g=A.year(),I=i),{week:I,year:g}}function wA(A,M,t){var I=jA(A,M,t),g=jA(A+1,M,t);return(BA(A)-I+g)/7}function lA(A){return uA(A,this._week.dow,this._week.doy).week}function LA(){return this._week.dow}function YA(){return this._week.doy}function dA(A){var M=this.localeData().week(this);return null==A?M:this.add(7*(A-M),"d")}function hA(A){var M=uA(this,1,4).week;return null==A?M:this.add(7*(A-M),"d")}function SA(A,M){return"string"!=typeof A?A:isNaN(A)?(A=M.weekdaysParse(A),"number"==typeof A?A:null):parseInt(A,10)}function zA(A,M){return"string"==typeof A?M.weekdaysParse(A)%7||7:isNaN(A)?null:A}function pA(A,M){return A?I(this._weekdays)?this._weekdays[A.day()]:this._weekdays[this._weekdays.isFormat.test(M)?"format":"standalone"][A.day()]:this._weekdays}function UA(A){return A?this._weekdaysShort[A.day()]:this._weekdaysShort}function OA(A){return A?this._weekdaysMin[A.day()]:this._weekdaysMin}function mA(A,M,t){var I,g,e,i=A.toLocaleLowerCase();if(!this._weekdaysParse)for(this._weekdaysParse=[],this._shortWeekdaysParse=[],this._minWeekdaysParse=[],I=0;I<7;++I)e=n([2e3,1]).day(I),this._minWeekdaysParse[I]=this.weekdaysMin(e,"").toLocaleLowerCase(),this._shortWeekdaysParse[I]=this.weekdaysShort(e,"").toLocaleLowerCase(),this._weekdaysParse[I]=this.weekdays(e,"").toLocaleLowerCase();return t?"dddd"===M?(g=og.call(this._weekdaysParse,i),g!==-1?g:null):"ddd"===M?(g=og.call(this._shortWeekdaysParse,i),g!==-1?g:null):(g=og.call(this._minWeekdaysParse,i),g!==-1?g:null):"dddd"===M?(g=og.call(this._weekdaysParse,i),g!==-1?g:(g=og.call(this._shortWeekdaysParse,i),g!==-1?g:(g=og.call(this._minWeekdaysParse,i),g!==-1?g:null))):"ddd"===M?(g=og.call(this._shortWeekdaysParse,i),g!==-1?g:(g=og.call(this._weekdaysParse,i),g!==-1?g:(g=og.call(this._minWeekdaysParse,i),g!==-1?g:null))):(g=og.call(this._minWeekdaysParse,i),g!==-1?g:(g=og.call(this._weekdaysParse,i),g!==-1?g:(g=og.call(this._shortWeekdaysParse,i),g!==-1?g:null)))}function FA(A,M,t){var I,g,e;if(this._weekdaysParseExact)return mA.call(this,A,M,t);for(this._weekdaysParse||(this._weekdaysParse=[],this._minWeekdaysParse=[],this._shortWeekdaysParse=[],this._fullWeekdaysParse=[]),I=0;I<7;I++){if(g=n([2e3,1]).day(I),t&&!this._fullWeekdaysParse[I]&&(this._fullWeekdaysParse[I]=new RegExp("^"+this.weekdays(g,"").replace(".",".?")+"$","i"),this._shortWeekdaysParse[I]=new RegExp("^"+this.weekdaysShort(g,"").replace(".",".?")+"$","i"),this._minWeekdaysParse[I]=new RegExp("^"+this.weekdaysMin(g,"").replace(".",".?")+"$","i")),this._weekdaysParse[I]||(e="^"+this.weekdays(g,"")+"|^"+this.weekdaysShort(g,"")+"|^"+this.weekdaysMin(g,""),this._weekdaysParse[I]=new RegExp(e.replace(".",""),"i")),t&&"dddd"===M&&this._fullWeekdaysParse[I].test(A))return I;if(t&&"ddd"===M&&this._shortWeekdaysParse[I].test(A))return I;if(t&&"dd"===M&&this._minWeekdaysParse[I].test(A))return I;if(!t&&this._weekdaysParse[I].test(A))return I}}function fA(A){if(!this.isValid())return null!=A?this:NaN;var M=this._isUTC?this._d.getUTCDay():this._d.getDay();return null!=A?(A=SA(A,this.localeData()),this.add(A-M,"d")):M}function kA(A){if(!this.isValid())return null!=A?this:NaN;var M=(this.day()+7-this.localeData()._week.dow)%7;return null==A?M:this.add(A-M,"d")}function RA(A){if(!this.isValid())return null!=A?this:NaN;if(null!=A){var M=zA(A,this.localeData());return this.day(this.day()%7?M:M-7)}return this.day()||7}function JA(A){return this._weekdaysParseExact?(N(this,"_weekdaysRegex")||bA.call(this),A?this._weekdaysStrictRegex:this._weekdaysRegex):(N(this,"_weekdaysRegex")||(this._weekdaysRegex=jg),this._weekdaysStrictRegex&&A?this._weekdaysStrictRegex:this._weekdaysRegex)}function GA(A){return this._weekdaysParseExact?(N(this,"_weekdaysRegex")||bA.call(this),A?this._weekdaysShortStrictRegex:this._weekdaysShortRegex):(N(this,"_weekdaysShortRegex")||(this._weekdaysShortRegex=yg),this._weekdaysShortStrictRegex&&A?this._weekdaysShortStrictRegex:this._weekdaysShortRegex)}function HA(A){return this._weekdaysParseExact?(N(this,"_weekdaysRegex")||bA.call(this),A?this._weekdaysMinStrictRegex:this._weekdaysMinRegex):(N(this,"_weekdaysMinRegex")||(this._weekdaysMinRegex=ug),this._weekdaysMinStrictRegex&&A?this._weekdaysMinStrictRegex:this._weekdaysMinRegex)}function bA(){function A(A,M){return M.length-A.length}var M,t,I,g,e,i=[],T=[],E=[],N=[];for(M=0;M<7;M++)t=n([2e3,1]).day(M),I=this.weekdaysMin(t,""),g=this.weekdaysShort(t,""),e=this.weekdays(t,""),i.push(I),T.push(g),E.push(e),N.push(I),N.push(g),N.push(e);for(i.sort(A),T.sort(A),E.sort(A),N.sort(A),M=0;M<7;M++)T[M]=MA(T[M]),E[M]=MA(E[M]),N[M]=MA(N[M]);this._weekdaysRegex=new RegExp("^("+N.join("|")+")","i"),this._weekdaysShortRegex=this._weekdaysRegex,this._weekdaysMinRegex=this._weekdaysRegex,this._weekdaysStrictRegex=new RegExp("^("+E.join("|")+")","i"),this._weekdaysShortStrictRegex=new RegExp("^("+T.join("|")+")","i"),this._weekdaysMinStrictRegex=new RegExp("^("+i.join("|")+")","i")}function vA(){return this.hours()%12||12}function XA(){return this.hours()||24}function WA(A,M){V(A,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),M)})}function VA(A,M){return M._meridiemParse}function PA(A){return"p"===(A+"").toLowerCase().charAt(0)}function ZA(A,M,t){return A>11?t?"pm":"PM":t?"am":"AM"}function KA(A){return A?A.toLowerCase().replace("_","-"):A}function qA(A){for(var M,t,I,g,e=0;e0;){if(I=_A(g.slice(0,M).join("-")))return I;if(t&&t.length>=M&&y(g,t,!0)>=M-1)break;M--}e++}return null}function _A(M){var t=null;if(!dg[M]&&"undefined"!=typeof A&&A&&A.exports)try{t=wg._abbr,!function(){var A=new Error('Cannot find module "./locale"');throw A.code="MODULE_NOT_FOUND",A}(),$A(t)}catch(A){}return dg[M]}function $A(A,M){var t;return A&&(t=B(M)?tM(A):AM(A,M),t&&(wg=t)),wg._abbr}function AM(A,M){if(null!==M){var t=Yg;if(M.abbr=A,null!=dg[A])l("defineLocaleOverride","use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale See http://momentjs.com/guides/#/warnings/define-locale/ for more info."),t=dg[A]._config;else if(null!=M.parentLocale){if(null==dg[M.parentLocale])return hg[M.parentLocale]||(hg[M.parentLocale]=[]),hg[M.parentLocale].push({name:A,config:M}),null;t=dg[M.parentLocale]._config}return dg[A]=new h(d(t,M)),hg[A]&&hg[A].forEach(function(A){AM(A.name,A.config)}),$A(A),dg[A]}return delete dg[A],null}function MM(A,M){if(null!=M){var t,I=Yg;null!=dg[A]&&(I=dg[A]._config),M=d(I,M),t=new h(M),t.parentLocale=dg[A],dg[A]=t,$A(A)}else null!=dg[A]&&(null!=dg[A].parentLocale?dg[A]=dg[A].parentLocale:null!=dg[A]&&delete dg[A]);return dg[A]}function tM(A){var M;if(A&&A._locale&&A._locale._abbr&&(A=A._locale._abbr),!A)return wg;if(!I(A)){if(M=_A(A))return M;A=[A]}return qA(A)}function IM(){return uI(dg)}function gM(A){var M,t=A._a;return t&&c(A).overflow===-2&&(M=t[tg]<0||t[tg]>11?tg:t[Ig]<1||t[Ig]>eA(t[Mg],t[tg])?Ig:t[gg]<0||t[gg]>24||24===t[gg]&&(0!==t[eg]||0!==t[ig]||0!==t[Tg])?gg:t[eg]<0||t[eg]>59?eg:t[ig]<0||t[ig]>59?ig:t[Tg]<0||t[Tg]>999?Tg:-1,c(A)._overflowDayOfYear&&(MIg)&&(M=Ig),c(A)._overflowWeeks&&M===-1&&(M=Eg),c(A)._overflowWeekday&&M===-1&&(M=Ng),c(A).overflow=M),A}function eM(A){var M,t,I,g,e,i,T=A._i,E=Sg.exec(T)||zg.exec(T);if(E){for(c(A).iso=!0,M=0,t=Ug.length;MBA(g)&&(c(A)._overflowDayOfYear=!0),t=xA(g,0,A._dayOfYear),A._a[tg]=t.getUTCMonth(),A._a[Ig]=t.getUTCDate()),M=0;M<3&&null==A._a[M];++M)A._a[M]=e[M]=I[M];for(;M<7;M++)A._a[M]=e[M]=null==A._a[M]?2===M?1:0:A._a[M];24===A._a[gg]&&0===A._a[eg]&&0===A._a[ig]&&0===A._a[Tg]&&(A._nextDay=!0,A._a[gg]=0),A._d=(A._useUTC?xA:sA).apply(null,e),null!=A._tzm&&A._d.setUTCMinutes(A._d.getUTCMinutes()-A._tzm),A._nextDay&&(A._a[gg]=24)}}function oM(A){var M,t,I,g,e,i,T,E;if(M=A._w,null!=M.GG||null!=M.W||null!=M.E)e=1,i=4,t=TM(M.GG,A._a[Mg],uA(sM(),1,4).year),I=TM(M.W,1),g=TM(M.E,1),(g<1||g>7)&&(E=!0);else{e=A._locale._week.dow,i=A._locale._week.doy;var N=uA(sM(),e,i);t=TM(M.gg,A._a[Mg],N.year),I=TM(M.w,N.week),null!=M.d?(g=M.d,(g<0||g>6)&&(E=!0)):null!=M.e?(g=M.e+e,(M.e<0||M.e>6)&&(E=!0)):g=e}I<1||I>wA(t,e,i)?c(A)._overflowWeeks=!0:null!=E?c(A)._overflowWeekday=!0:(T=yA(t,I,g,e,i),A._a[Mg]=T.year,A._dayOfYear=T.dayOfYear)}function nM(A){if(A._f===M.ISO_8601)return void eM(A);A._a=[],c(A).empty=!0;var t,I,g,e,i,T=""+A._i,E=T.length,N=0;for(g=q(A._f,A._locale).match(pI)||[],t=0;t0&&c(A).unusedInput.push(i),T=T.slice(T.indexOf(I)+I.length),N+=I.length),mI[e]?(I?c(A).empty=!1:c(A).unusedTokens.push(e),gA(e,I,A)):A._strict&&!I&&c(A).unusedTokens.push(e);c(A).charsLeftOver=E-N,T.length>0&&c(A).unusedInput.push(T),A._a[gg]<=12&&c(A).bigHour===!0&&A._a[gg]>0&&(c(A).bigHour=void 0),c(A).parsedDateParts=A._a.slice(0),c(A).meridiem=A._meridiem,A._a[gg]=CM(A._locale,A._a[gg],A._meridiem),NM(A),gM(A)}function CM(A,M,t){var I;return null==t?M:null!=A.meridiemHour?A.meridiemHour(M,t):null!=A.isPM?(I=A.isPM(t),I&&M<12&&(M+=12),I||12!==M||(M=0),M):M}function cM(A){var M,t,I,g,e;if(0===A._f.length)return c(A).invalidFormat=!0,void(A._d=new Date(NaN));for(g=0;gthis.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()}function fM(){if(!B(this._isDSTShifted))return this._isDSTShifted;var A={};if(Q(A,this),A=BM(A),A._a){var M=A._isUTC?n(A._a):sM(A._a);this._isDSTShifted=this.isValid()&&y(A._a,M.toArray())>0}else this._isDSTShifted=!1;return this._isDSTShifted}function kM(){return!!this.isValid()&&!this._isUTC}function RM(){return!!this.isValid()&&this._isUTC}function JM(){return!!this.isValid()&&(this._isUTC&&0===this._offset)}function GM(A,M){var t,I,g,e=A,T=null;return wM(A)?e={ms:A._milliseconds,d:A._days,M:A._months}:i(A)?(e={},M?e[M]=A:e.milliseconds=A):(T=Jg.exec(A))?(t="-"===T[1]?-1:1,e={y:0,d:j(T[Ig])*t,h:j(T[gg])*t,m:j(T[eg])*t,s:j(T[ig])*t,ms:j(lM(1e3*T[Tg]))*t}):(T=Gg.exec(A))?(t="-"===T[1]?-1:1,e={y:HM(T[2],t),M:HM(T[3],t),w:HM(T[4],t),d:HM(T[5],t),h:HM(T[6],t),m:HM(T[7],t),s:HM(T[8],t)}):null==e?e={}:"object"==typeof e&&("from"in e||"to"in e)&&(g=vM(sM(e.from),sM(e.to)),e={},e.ms=g.milliseconds,e.M=g.months),I=new uM(e),wM(A)&&N(A,"_locale")&&(I._locale=A._locale),I}function HM(A,M){var t=A&&parseFloat(A.replace(",","."));return(isNaN(t)?0:t)*M}function bM(A,M){var t={milliseconds:0,months:0};return t.months=M.month()-A.month()+12*(M.year()-A.year()),A.clone().add(t.months,"M").isAfter(M)&&--t.months,t.milliseconds=+M-+A.clone().add(t.months,"M"),t}function vM(A,M){var t;return A.isValid()&&M.isValid()?(M=dM(M,A),A.isBefore(M)?t=bM(A,M):(t=bM(M,A),t.milliseconds=-t.milliseconds,t.months=-t.months),t):{milliseconds:0,months:0}}function XM(A,M){return function(t,I){var g,e;return null===I||isNaN(+I)||(l(M,"moment()."+M+"(period, number) is deprecated. Please use moment()."+M+"(number, period). See http://momentjs.com/guides/#/warnings/add-inverted-param/ for more info."),e=t,t=I,I=e),t="string"==typeof t?+t:t,g=GM(t,I),WM(this,g,A),this}}function WM(A,t,I,g){var e=t._milliseconds,i=lM(t._days),T=lM(t._months);A.isValid()&&(g=null==g||g,e&&A._d.setTime(A._d.valueOf()+e*I),i&&b(A,"Date",H(A,"Date")+i*I),T&&oA(A,H(A,"Month")+T*I),g&&M.updateOffset(A,i||T))}function VM(A,M){var t=A.diff(M,"days",!0);return t<-6?"sameElse":t<-1?"lastWeek":t<0?"lastDay":t<1?"sameDay":t<2?"nextDay":t<7?"nextWeek":"sameElse"}function PM(A,t){var I=A||sM(),g=dM(I,this).startOf("day"),e=M.calendarFormat(this,g)||"sameElse",i=t&&(L(t[e])?t[e].call(this,I):t[e]);return this.format(i||this.localeData().calendar(e,this,sM(I)))}function ZM(){return new r(this)}function KM(A,M){var t=s(A)?A:sM(A);return!(!this.isValid()||!t.isValid())&&(M=f(B(M)?"millisecond":M),"millisecond"===M?this.valueOf()>t.valueOf():t.valueOf()e&&(M=e),pt.call(this,A,M,t,I,g))}function pt(A,M,t,I,g){var e=yA(A,M,t,I,g),i=xA(e.year,0,e.dayOfYear);return this.year(i.getUTCFullYear()),this.month(i.getUTCMonth()),this.date(i.getUTCDate()),this}function Ut(A){return null==A?Math.ceil((this.month()+1)/3):this.month(3*(A-1)+this.month()%3)}function Ot(A){var M=Math.round((this.clone().startOf("day")-this.clone().startOf("year"))/864e5)+1;return null==A?M:this.add(A-M,"d")}function mt(A,M){M[Tg]=j(1e3*("0."+A))}function Ft(){return this._isUTC?"UTC":""}function ft(){return this._isUTC?"Coordinated Universal Time":""}function kt(A){return sM(1e3*A)}function Rt(){return sM.apply(null,arguments).parseZone()}function Jt(A){return A}function Gt(A,M,t,I){var g=tM(),e=n().set(I,M);return g[t](e,A)}function Ht(A,M,t){if(i(A)&&(M=A,A=void 0),A=A||"",null!=M)return Gt(A,M,t,"month");var I,g=[];for(I=0;I<12;I++)g[I]=Gt(A,I,t,"month");return g}function bt(A,M,t,I){"boolean"==typeof A?(i(M)&&(t=M,M=void 0),M=M||""):(M=A,t=M,A=!1,i(M)&&(t=M,M=void 0),M=M||"");var g=tM(),e=A?g._week.dow:0;if(null!=t)return Gt(M,(t+e)%7,I,"day");var T,E=[];for(T=0;T<7;T++)E[T]=Gt(M,(T+e)%7,I,"day");return E}function vt(A,M){return Ht(A,M,"months")}function Xt(A,M){return Ht(A,M,"monthsShort")}function Wt(A,M,t){return bt(A,M,t,"weekdays")}function Vt(A,M,t){return bt(A,M,t,"weekdaysShort")}function Pt(A,M,t){return bt(A,M,t,"weekdaysMin")}function Zt(){var A=this._data;return this._milliseconds=_g(this._milliseconds),this._days=_g(this._days),this._months=_g(this._months),A.milliseconds=_g(A.milliseconds),A.seconds=_g(A.seconds),A.minutes=_g(A.minutes),A.hours=_g(A.hours),A.months=_g(A.months),A.years=_g(A.years),this}function Kt(A,M,t,I){var g=GM(M,t);return A._milliseconds+=I*g._milliseconds,A._days+=I*g._days,A._months+=I*g._months,A._bubble()}function qt(A,M){return Kt(this,A,M,1)}function _t(A,M){return Kt(this,A,M,-1)}function $t(A){return A<0?Math.floor(A):Math.ceil(A)}function AI(){var A,M,t,I,g,e=this._milliseconds,i=this._days,T=this._months,E=this._data;return e>=0&&i>=0&&T>=0||e<=0&&i<=0&&T<=0||(e+=864e5*$t(tI(T)+i),i=0,T=0),E.milliseconds=e%1e3,A=x(e/1e3),E.seconds=A%60,M=x(A/60),E.minutes=M%60,t=x(M/60),E.hours=t%24,i+=x(t/24),g=x(MI(i)),T+=g,i-=$t(tI(g)),I=x(T/12),T%=12,E.days=i,E.months=T,E.years=I,this}function MI(A){return 4800*A/146097}function tI(A){return 146097*A/4800}function II(A){var M,t,I=this._milliseconds;if(A=f(A),"month"===A||"year"===A)return M=this._days+I/864e5,t=this._months+MI(M),"month"===A?t:t/12;switch(M=this._days+Math.round(tI(this._months)),A){case"week":return M/7+I/6048e5;case"day":return M+I/864e5;case"hour":return 24*M+I/36e5;case"minute":return 1440*M+I/6e4;case"second":return 86400*M+I/1e3;case"millisecond":return Math.floor(864e5*M)+I;default:throw new Error("Unknown unit "+A)}}function gI(){return this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*j(this._months/12)}function eI(A){return function(){return this.as(A)}}function iI(A){return A=f(A),this[A+"s"]()}function TI(A){return function(){return this._data[A]}}function EI(){return x(this.days()/7)}function NI(A,M,t,I,g){return g.relativeTime(M||1,!!t,A,I)}function oI(A,M,t){var I=GM(A).abs(),g=De(I.as("s")),e=De(I.as("m")),i=De(I.as("h")),T=De(I.as("d")),E=De(I.as("M")),N=De(I.as("y")),o=g0,o[4]=t,NI.apply(null,o)}function nI(A){return void 0===A?De:"function"==typeof A&&(De=A,!0)}function CI(A,M){return void 0!==ae[A]&&(void 0===M?ae[A]:(ae[A]=M,!0))}function cI(A){var M=this.localeData(),t=oI(this,!A,M);return A&&(t=M.pastFuture(+this,t)),M.postformat(t)}function DI(){var A,M,t,I=Be(this._milliseconds)/1e3,g=Be(this._days),e=Be(this._months);A=x(I/60),M=x(A/60),I%=60,A%=60,t=x(e/12),e%=12;var i=t,T=e,E=g,N=M,o=A,n=I,C=this.asSeconds();return C?(C<0?"-":"")+"P"+(i?i+"Y":"")+(T?T+"M":"")+(E?E+"D":"")+(N||o||n?"T":"")+(N?N+"H":"")+(o?o+"M":"")+(n?n+"S":""):"P0D"}var aI,BI;BI=Array.prototype.some?Array.prototype.some:function(A){for(var M=Object(this),t=M.length>>>0,I=0;I68?1900:2e3)};var Bg=G("FullYear",!0);V("w",["ww",2],"wo","week"),V("W",["WW",2],"Wo","isoWeek"),F("week","w"),F("isoWeek","W"),R("week",5),R("isoWeek",5),_("w",GI),_("ww",GI,fI),_("W",GI),_("WW",GI,fI),IA(["w","ww","W","WW"],function(A,M,t,I){M[I.substr(0,1)]=j(A)});var Qg={dow:0,doy:6};V("d",0,"do","day"),V("dd",0,0,function(A){return this.localeData().weekdaysMin(this,A)}),V("ddd",0,0,function(A){return this.localeData().weekdaysShort(this,A)}),V("dddd",0,0,function(A){return this.localeData().weekdays(this,A)}),V("e",0,0,"weekday"),V("E",0,0,"isoWeekday"),F("day","d"),F("weekday","e"),F("isoWeekday","E"),R("day",11),R("weekday",11),R("isoWeekday",11),_("d",GI),_("e",GI),_("E",GI),_("dd",function(A,M){return M.weekdaysMinRegex(A)}),_("ddd",function(A,M){return M.weekdaysShortRegex(A)}),_("dddd",function(A,M){return M.weekdaysRegex(A)}),IA(["dd","ddd","dddd"],function(A,M,t,I){var g=t._locale.weekdaysParse(A,I,t._strict);null!=g?M.d=g:c(t).invalidWeekday=A}),IA(["d","e","E"],function(A,M,t,I){M[I]=j(A)});var rg="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),sg="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),xg="Su_Mo_Tu_We_Th_Fr_Sa".split("_"),jg=_I,yg=_I,ug=_I;V("H",["HH",2],0,"hour"),V("h",["hh",2],0,vA),V("k",["kk",2],0,XA),V("hmm",0,0,function(){return""+vA.apply(this)+W(this.minutes(),2)}),V("hmmss",0,0,function(){return""+vA.apply(this)+W(this.minutes(),2)+W(this.seconds(),2)}),V("Hmm",0,0,function(){return""+this.hours()+W(this.minutes(),2)}),V("Hmmss",0,0,function(){return""+this.hours()+W(this.minutes(),2)+W(this.seconds(),2)}),WA("a",!0),WA("A",!1),F("hour","h"),R("hour",13),_("a",VA),_("A",VA),_("H",GI),_("h",GI),_("HH",GI,fI),_("hh",GI,fI),_("hmm",HI),_("hmmss",bI),_("Hmm",HI),_("Hmmss",bI),tA(["H","HH"],gg),tA(["a","A"],function(A,M,t){t._isPm=t._locale.isPM(A),t._meridiem=A}),tA(["h","hh"],function(A,M,t){M[gg]=j(A),c(t).bigHour=!0}),tA("hmm",function(A,M,t){var I=A.length-2;M[gg]=j(A.substr(0,I)),M[eg]=j(A.substr(I)),c(t).bigHour=!0}),tA("hmmss",function(A,M,t){var I=A.length-4,g=A.length-2;M[gg]=j(A.substr(0,I)),M[eg]=j(A.substr(I,2)),M[ig]=j(A.substr(g)),c(t).bigHour=!0}),tA("Hmm",function(A,M,t){var I=A.length-2;M[gg]=j(A.substr(0,I)),M[eg]=j(A.substr(I))}),tA("Hmmss",function(A,M,t){var I=A.length-4,g=A.length-2;M[gg]=j(A.substr(0,I)),M[eg]=j(A.substr(I,2)),M[ig]=j(A.substr(g))});var wg,lg=/[ap]\.?m?\.?/i,Lg=G("Hours",!0),Yg={calendar:wI,longDateFormat:lI,invalidDate:LI,ordinal:YI,ordinalParse:dI,relativeTime:hI,months:Cg,monthsShort:cg,week:Qg,weekdays:rg,weekdaysMin:xg,weekdaysShort:sg,meridiemParse:lg},dg={},hg={},Sg=/^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,zg=/^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,pg=/Z|[+-]\d\d(?::?\d\d)?/,Ug=[["YYYYYY-MM-DD",/[+-]\d{6}-\d\d-\d\d/],["YYYY-MM-DD",/\d{4}-\d\d-\d\d/],["GGGG-[W]WW-E",/\d{4}-W\d\d-\d/],["GGGG-[W]WW",/\d{4}-W\d\d/,!1],["YYYY-DDD",/\d{4}-\d{3}/],["YYYY-MM",/\d{4}-\d\d/,!1],["YYYYYYMMDD",/[+-]\d{10}/],["YYYYMMDD",/\d{8}/],["GGGG[W]WWE",/\d{4}W\d{3}/],["GGGG[W]WW",/\d{4}W\d{2}/,!1],["YYYYDDD",/\d{7}/]],Og=[["HH:mm:ss.SSSS",/\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss,SSSS",/\d\d:\d\d:\d\d,\d+/],["HH:mm:ss",/\d\d:\d\d:\d\d/],["HH:mm",/\d\d:\d\d/],["HHmmss.SSSS",/\d\d\d\d\d\d\.\d+/],["HHmmss,SSSS",/\d\d\d\d\d\d,\d+/],["HHmmss",/\d\d\d\d\d\d/],["HHmm",/\d\d\d\d/],["HH",/\d\d/]],mg=/^\/?Date\((\-?\d+)/i;M.createFromInputFallback=w("value provided is not in a recognized ISO format. moment construction falls back to js Date(), which is not reliable across all browsers and versions. Non ISO date formats are discouraged and will be removed in an upcoming major release. Please refer to http://momentjs.com/guides/#/warnings/js-date/ for more info.",function(A){A._d=new Date(A._i+(A._useUTC?" UTC":""))}),M.ISO_8601=function(){};var Fg=w("moment().min is deprecated, use moment.max instead. http://momentjs.com/guides/#/warnings/min-max/",function(){var A=sM.apply(null,arguments);return this.isValid()&&A.isValid()?Athis?this:A:a()}),kg=function(){return Date.now?Date.now():+new Date};LM("Z",":"),LM("ZZ",""),_("Z",KI),_("ZZ",KI),tA(["Z","ZZ"],function(A,M,t){t._useUTC=!0,t._tzm=YM(KI,A)});var Rg=/([\+\-]|\d\d)/gi;M.updateOffset=function(){};var Jg=/^(\-)?(?:(\d*)[. ])?(\d+)\:(\d+)(?:\:(\d+)(\.\d*)?)?$/,Gg=/^(-)?P(?:(-?[0-9,.]*)Y)?(?:(-?[0-9,.]*)M)?(?:(-?[0-9,.]*)W)?(?:(-?[0-9,.]*)D)?(?:T(?:(-?[0-9,.]*)H)?(?:(-?[0-9,.]*)M)?(?:(-?[0-9,.]*)S)?)?$/;GM.fn=uM.prototype;var Hg=XM(1,"add"),bg=XM(-1,"subtract");M.defaultFormat="YYYY-MM-DDTHH:mm:ssZ",M.defaultFormatUtc="YYYY-MM-DDTHH:mm:ss[Z]";var vg=w("moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.",function(A){return void 0===A?this.localeData():this.locale(A)});V(0,["gg",2],0,function(){return this.weekYear()%100}),V(0,["GG",2],0,function(){return this.isoWeekYear()%100}),Lt("gggg","weekYear"),Lt("ggggg","weekYear"),Lt("GGGG","isoWeekYear"),Lt("GGGGG","isoWeekYear"),F("weekYear","gg"),F("isoWeekYear","GG"),R("weekYear",1),R("isoWeekYear",1),_("G",PI),_("g",PI),_("GG",GI,fI),_("gg",GI,fI),_("GGGG",XI,RI),_("gggg",XI,RI),_("GGGGG",WI,JI),_("ggggg",WI,JI),IA(["gggg","ggggg","GGGG","GGGGG"],function(A,M,t,I){M[I.substr(0,2)]=j(A)}),IA(["gg","GG"],function(A,t,I,g){t[g]=M.parseTwoDigitYear(A)}),V("Q",0,"Qo","quarter"),F("quarter","Q"),R("quarter",7),_("Q",FI),tA("Q",function(A,M){M[tg]=3*(j(A)-1)}),V("D",["DD",2],"Do","date"),F("date","D"),R("date",9),_("D",GI),_("DD",GI,fI),_("Do",function(A,M){return A?M._ordinalParse:M._ordinalParseLenient}),tA(["D","DD"],Ig),tA("Do",function(A,M){M[Ig]=j(A.match(GI)[0],10)});var Xg=G("Date",!0);V("DDD",["DDDD",3],"DDDo","dayOfYear"),F("dayOfYear","DDD"),R("dayOfYear",4),_("DDD",vI),_("DDDD",kI),tA(["DDD","DDDD"],function(A,M,t){t._dayOfYear=j(A)}),V("m",["mm",2],0,"minute"),F("minute","m"),R("minute",14),_("m",GI),_("mm",GI,fI),tA(["m","mm"],eg);var Wg=G("Minutes",!1);V("s",["ss",2],0,"second"),F("second","s"),R("second",15),_("s",GI),_("ss",GI,fI),tA(["s","ss"],ig);var Vg=G("Seconds",!1);V("S",0,0,function(){return~~(this.millisecond()/100)}),V(0,["SS",2],0,function(){return~~(this.millisecond()/10)}),V(0,["SSS",3],0,"millisecond"),V(0,["SSSS",4],0,function(){return 10*this.millisecond()}),V(0,["SSSSS",5],0,function(){return 100*this.millisecond()}),V(0,["SSSSSS",6],0,function(){return 1e3*this.millisecond()}),V(0,["SSSSSSS",7],0,function(){return 1e4*this.millisecond()}),V(0,["SSSSSSSS",8],0,function(){return 1e5*this.millisecond()}),V(0,["SSSSSSSSS",9],0,function(){return 1e6*this.millisecond()}),F("millisecond","ms"),R("millisecond",16),_("S",vI,FI),_("SS",vI,fI),_("SSS",vI,kI);var Pg;for(Pg="SSSS";Pg.length<=9;Pg+="S")_(Pg,VI);for(Pg="S";Pg.length<=9;Pg+="S")tA(Pg,mt);var Zg=G("Milliseconds",!1);V("z",0,0,"zoneAbbr"),V("zz",0,0,"zoneName");var Kg=r.prototype;Kg.add=Hg,Kg.calendar=PM,Kg.clone=ZM,Kg.diff=tt,Kg.endOf=at,Kg.format=Tt,Kg.from=Et,Kg.fromNow=Nt,Kg.to=ot,Kg.toNow=nt,Kg.get=v,Kg.invalidAt=wt,Kg.isAfter=KM,Kg.isBefore=qM,Kg.isBetween=_M,Kg.isSame=$M,Kg.isSameOrAfter=At,Kg.isSameOrBefore=Mt,Kg.isValid=yt,Kg.lang=vg,Kg.locale=Ct,Kg.localeData=ct,Kg.max=fg,Kg.min=Fg,Kg.parsingFlags=ut,Kg.set=X,Kg.startOf=Dt,Kg.subtract=bg,Kg.toArray=st,Kg.toObject=xt,Kg.toDate=rt,Kg.toISOString=et,Kg.inspect=it,Kg.toJSON=jt,Kg.toString=gt,Kg.unix=Qt,Kg.valueOf=Bt,Kg.creationData=lt,Kg.year=Bg,Kg.isLeapYear=rA,Kg.weekYear=Yt,Kg.isoWeekYear=dt,Kg.quarter=Kg.quarters=Ut,Kg.month=nA,Kg.daysInMonth=CA,Kg.week=Kg.weeks=dA,Kg.isoWeek=Kg.isoWeeks=hA,Kg.weeksInYear=St,Kg.isoWeeksInYear=ht,Kg.date=Xg,Kg.day=Kg.days=fA,Kg.weekday=kA,Kg.isoWeekday=RA,Kg.dayOfYear=Ot,Kg.hour=Kg.hours=Lg,Kg.minute=Kg.minutes=Wg,Kg.second=Kg.seconds=Vg,Kg.millisecond=Kg.milliseconds=Zg,Kg.utcOffset=SM,Kg.utc=pM,Kg.local=UM,Kg.parseZone=OM,Kg.hasAlignedHourOffset=mM,Kg.isDST=FM,Kg.isLocal=kM,Kg.isUtcOffset=RM,Kg.isUtc=JM,Kg.isUTC=JM,Kg.zoneAbbr=Ft,Kg.zoneName=ft,Kg.dates=w("dates accessor is deprecated. Use date instead.",Xg),Kg.months=w("months accessor is deprecated. Use month instead",nA),Kg.years=w("years accessor is deprecated. Use year instead",Bg),Kg.zone=w("moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/",zM),Kg.isDSTShifted=w("isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information",fM);var qg=h.prototype;qg.calendar=S,qg.longDateFormat=z,qg.invalidDate=p,qg.ordinal=U,qg.preparse=Jt,qg.postformat=Jt,qg.relativeTime=O,qg.pastFuture=m,qg.set=Y,qg.months=iA,qg.monthsShort=TA,qg.monthsParse=NA,qg.monthsRegex=DA,qg.monthsShortRegex=cA,qg.week=lA,qg.firstDayOfYear=YA,qg.firstDayOfWeek=LA,qg.weekdays=pA,qg.weekdaysMin=OA,qg.weekdaysShort=UA,qg.weekdaysParse=FA,qg.weekdaysRegex=JA,qg.weekdaysShortRegex=GA,qg.weekdaysMinRegex=HA,qg.isPM=PA,qg.meridiem=ZA,$A("en",{ordinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(A){var M=A%10,t=1===j(A%100/10)?"th":1===M?"st":2===M?"nd":3===M?"rd":"th";return A+t}}),M.lang=w("moment.lang is deprecated. Use moment.locale instead.",$A),M.langData=w("moment.langData is deprecated. Use moment.localeData instead.",tM);var _g=Math.abs,$g=eI("ms"),Ae=eI("s"),Me=eI("m"),te=eI("h"),Ie=eI("d"),ge=eI("w"),ee=eI("M"),ie=eI("y"),Te=TI("milliseconds"),Ee=TI("seconds"),Ne=TI("minutes"),oe=TI("hours"),ne=TI("days"),Ce=TI("months"),ce=TI("years"),De=Math.round,ae={s:45,m:45,h:22,d:26,M:11},Be=Math.abs,Qe=uM.prototype;return Qe.abs=Zt,Qe.add=qt,Qe.subtract=_t,Qe.as=II,Qe.asMilliseconds=$g,Qe.asSeconds=Ae,Qe.asMinutes=Me,Qe.asHours=te,Qe.asDays=Ie,Qe.asWeeks=ge,Qe.asMonths=ee,Qe.asYears=ie,Qe.valueOf=gI,Qe._bubble=AI,Qe.get=iI,Qe.milliseconds=Te,Qe.seconds=Ee,Qe.minutes=Ne,Qe.hours=oe,Qe.days=ne,Qe.weeks=EI,Qe.months=Ce,Qe.years=ce,Qe.humanize=cI,Qe.toISOString=DI,Qe.toString=DI,Qe.toJSON=DI,Qe.locale=Ct,Qe.localeData=ct,Qe.toIsoString=w("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",DI),Qe.lang=vg,V("X",0,0,"unix"),V("x",0,0,"valueOf"),_("x",PI),_("X",qI),tA("X",function(A,M,t){t._d=new Date(1e3*parseFloat(A,10))}),tA("x",function(A,M,t){t._d=new Date(j(A))}),M.version="2.17.1",t(sM),M.fn=Kg,M.min=jM,M.max=yM,M.now=kg,M.utc=n,M.unix=kt,M.months=vt,M.isDate=T,M.locale=$A,M.invalid=a,M.duration=GM,M.isMoment=s,M.weekdays=Wt,M.parseZone=Rt,M.localeData=tM,M.isDuration=wM,M.monthsShort=Xt,M.weekdaysMin=Pt,M.defineLocale=AM,M.updateLocale=MM,M.locales=IM,M.weekdaysShort=Vt,M.normalizeUnits=f,M.relativeTimeRounding=nI,M.relativeTimeThreshold=CI,M.calendarFormat=VM,M.prototype=Kg,M})}).call(M,t(128)(A))},function(A,M,t){"use strict";var I=t(130).default,g=t(131).default,e=t(78).default;M.__esModule=!0;var i=function(A){return I(g({values:function(){var A=this;return e(this).map(function(M){return A[M]})}}),A)},T={SIZES:{large:"lg",medium:"md",small:"sm",xsmall:"xs",lg:"lg",md:"md",sm:"sm",xs:"xs"},GRID_COLUMNS:12},E=i({LARGE:"large",MEDIUM:"medium",SMALL:"small",XSMALL:"xsmall"});M.Sizes=E;var N=i({SUCCESS:"success",WARNING:"warning",DANGER:"danger",INFO:"info"});M.State=N;var o="default";M.DEFAULT=o;var n="primary";M.PRIMARY=n;var C="link";M.LINK=C;var c="inverse";M.INVERSE=c,M.default=T},function(A,M){"use strict";function t(){for(var A=arguments.length,M=Array(A),t=0;t0?void 0:(0,C.default)(!1),null!=o&&(e+=encodeURI(o))):"("===E?g+=1:")"===E?g-=1:":"===E.charAt(0)?(N=E.substring(1),o=M[N],null!=o||g>0?void 0:(0,C.default)(!1),null!=o&&(e+=encodeURIComponent(o))):e+=E;return e.replace(/\/+/g,"/")}M.__esModule=!0,M.compilePattern=i,M.matchPattern=T,M.getParamNames=E,M.getParams=N,M.formatPattern=o;var n=t(8),C=I(n),c=Object.create(null)},function(A,M){"use strict";M.__esModule=!0;var t="PUSH";M.PUSH=t;var I="REPLACE";M.REPLACE=I;var g="POP";M.POP=g,M.default={PUSH:t,REPLACE:I,POP:g}},function(A,M,t){"use strict";function I(A,M){return(A&M)===M}var g=t(2),e={MUST_USE_ATTRIBUTE:1,MUST_USE_PROPERTY:2,HAS_SIDE_EFFECTS:4,HAS_BOOLEAN_VALUE:8,HAS_NUMERIC_VALUE:16,HAS_POSITIVE_NUMERIC_VALUE:48,HAS_OVERLOADED_BOOLEAN_VALUE:64,injectDOMPropertyConfig:function(A){var M=e,t=A.Properties||{},i=A.DOMAttributeNamespaces||{},E=A.DOMAttributeNames||{},N=A.DOMPropertyNames||{},o=A.DOMMutationMethods||{};A.isCustomAttribute&&T._isCustomAttributeFunctions.push(A.isCustomAttribute);for(var n in t){T.properties.hasOwnProperty(n)?g(!1):void 0;var C=n.toLowerCase(),c=t[n],D={attributeName:C,attributeNamespace:null,propertyName:n,mutationMethod:null,mustUseAttribute:I(c,M.MUST_USE_ATTRIBUTE),mustUseProperty:I(c,M.MUST_USE_PROPERTY),hasSideEffects:I(c,M.HAS_SIDE_EFFECTS),hasBooleanValue:I(c,M.HAS_BOOLEAN_VALUE),hasNumericValue:I(c,M.HAS_NUMERIC_VALUE),hasPositiveNumericValue:I(c,M.HAS_POSITIVE_NUMERIC_VALUE),hasOverloadedBooleanValue:I(c,M.HAS_OVERLOADED_BOOLEAN_VALUE)};if(D.mustUseAttribute&&D.mustUseProperty?g(!1):void 0,!D.mustUseProperty&&D.hasSideEffects?g(!1):void 0,D.hasBooleanValue+D.hasNumericValue+D.hasOverloadedBooleanValue<=1?void 0:g(!1),E.hasOwnProperty(n)){var a=E[n];D.attributeName=a}i.hasOwnProperty(n)&&(D.attributeNamespace=i[n]),N.hasOwnProperty(n)&&(D.propertyName=N[n]),o.hasOwnProperty(n)&&(D.mutationMethod=o[n]),T.properties[n]=D}}},i={},T={ID_ATTRIBUTE_NAME:"data-reactid",properties:{},getPossibleStandardName:null,_isCustomAttributeFunctions:[],isCustomAttribute:function(A){for(var M=0;M1){var M=A.indexOf(c,1);return M>-1?A.substr(0,M):A}return null},traverseEnterLeave:function(A,M,t,I,g){var e=N(A,M);e!==A&&o(A,e,t,I,!1,!0),e!==M&&o(e,M,t,g,!0,!1)},traverseTwoPhase:function(A,M,t){A&&(o("",A,M,t,!0,!1),o(A,"",M,t,!1,!0))},traverseTwoPhaseSkipTarget:function(A,M,t){A&&(o("",A,M,t,!0,!0),o(A,"",M,t,!0,!0))},traverseAncestors:function(A,M,t){o("",A,M,t,!0,!1)},getFirstCommonAncestorID:N,_getNextDescendantID:E,isAncestorIDOf:i,SEPARATOR:c};A.exports=B},function(A,M){var t=A.exports={version:"1.2.6"};"number"==typeof __e&&(__e=t)},function(A,M,t){"use strict";var I={};A.exports=I},function(A,M,t){"use strict";function I(A,M,t){var I=0;return n.default.Children.map(A,function(A){if(n.default.isValidElement(A)){var g=I;return I++,M.call(t,A,g)}return A})}function g(A,M,t){var I=0;return n.default.Children.forEach(A,function(A){n.default.isValidElement(A)&&(M.call(t,A,I),I++)})}function e(A){var M=0;return n.default.Children.forEach(A,function(A){n.default.isValidElement(A)&&M++}),M}function i(A){var M=!1;return n.default.Children.forEach(A,function(A){!M&&n.default.isValidElement(A)&&(M=!0)}),M}function T(A,M){var t=void 0;return g(A,function(I,g){!t&&M(I,g,A)&&(t=I)}),t}function E(A,M,t){var I=0,g=[];return n.default.Children.forEach(A,function(A){n.default.isValidElement(A)&&(M.call(t,A,I)&&g.push(A),I++)}),g}var N=t(6).default;M.__esModule=!0;var o=t(1),n=N(o);M.default={map:I,forEach:g,numberOf:e,find:T,findValidComponents:E,hasValidComponent:i},A.exports=M.default},function(A,M,t){"use strict";function I(A){return A&&A.__esModule?A:{default:A}}Object.defineProperty(M,"__esModule",{value:!0}),M.default=function(A){return(0,T.default)(e.default.findDOMNode(A))};var g=t(16),e=I(g),i=t(39),T=I(i);A.exports=M.default},function(A,M,t){"use strict";var I=t(202),g=t(436),e=t(215),i=t(224),T=t(225),E=t(2),N=(t(3),{}),o=null,n=function(A,M){A&&(g.executeDispatchesInOrder(A,M),A.isPersistent()||A.constructor.release(A))},C=function(A){return n(A,!0)},c=function(A){return n(A,!1)},D=null,a={injection:{injectMount:g.injection.injectMount,injectInstanceHandle:function(A){D=A},getInstanceHandle:function(){return D},injectEventPluginOrder:I.injectEventPluginOrder,injectEventPluginsByName:I.injectEventPluginsByName},eventNameDispatchConfigs:I.eventNameDispatchConfigs,registrationNameModules:I.registrationNameModules,putListener:function(A,M,t){"function"!=typeof t?E(!1):void 0;var g=N[M]||(N[M]={});g[A]=t;var e=I.registrationNameModules[M];e&&e.didPutListener&&e.didPutListener(A,M,t)},getListener:function(A,M){var t=N[M];return t&&t[A]},deleteListener:function(A,M){var t=I.registrationNameModules[M];t&&t.willDeleteListener&&t.willDeleteListener(A,M);var g=N[M];g&&delete g[A]},deleteAllListeners:function(A){for(var M in N)if(N[M][A]){var t=I.registrationNameModules[M];t&&t.willDeleteListener&&t.willDeleteListener(A,M),delete N[M][A]}},extractEvents:function(A,M,t,g,e){for(var T,E=I.plugins,N=0;N1?I-1:0),e=1;e":">","<":"<",'"':""","'":"'"},e=/[&><"']/g;A.exports=I},function(A,M,t){"use strict";var I=t(10),g=/^[ \r\n\t\f]/,e=/<(!--|link|noscript|meta|script|style)[ \r\n\t\f\/>]/,i=function(A,M){A.innerHTML=M};if("undefined"!=typeof MSApp&&MSApp.execUnsafeLocalFunction&&(i=function(A,M){MSApp.execUnsafeLocalFunction(function(){A.innerHTML=M})}),I.canUseDOM){var T=document.createElement("div");T.innerHTML=" ",""===T.innerHTML&&(i=function(A,M){if(A.parentNode&&A.parentNode.replaceChild(A,A),g.test(M)||"<"===M[0]&&e.test(M)){A.innerHTML=String.fromCharCode(65279)+M;var t=A.firstChild;1===t.data.length?A.removeChild(t):t.deleteData(0,1)}else A.innerHTML=M})}A.exports=i},function(A,M,t){"use strict";function I(A){return A&&A.__esModule?A:{default:A}}Object.defineProperty(M,"__esModule",{value:!0});var g=t(1),e=I(g),i=function(A){var M=A.label,t=A.id,I=A.name,g=A.value,i=A.onChange,T=A.type,E=A.spellCheck,N=A.required,o=A.readonly,n=A.autoComplete,C=A.align,c=A.className,D=e.default.createElement("input",{id:t,name:I,value:g,onChange:i,className:"ig-text",type:T,spellCheck:E,required:N,autoComplete:n});return o&&(D=e.default.createElement("input",{id:t,name:I,value:g,onChange:i,className:"ig-text",type:T,spellCheck:E,required:N,autoComplete:n,disabled:!0})),e.default.createElement("div",{className:"input-group "+C+" "+c},D,e.default.createElement("i",{className:"ig-helpers"}),e.default.createElement("label",{className:"ig-label"},M))};M.default=i},function(A,M,t){A.exports={default:t(258),__esModule:!0}},function(A,M,t){var I=t(264),g=t(49),e=t(132),i="prototype",T=function(A,M,t){var E,N,o,n=A&T.F,C=A&T.G,c=A&T.S,D=A&T.P,a=A&T.B,B=A&T.W,Q=C?g:g[M]||(g[M]={}),r=C?I:c?I[M]:(I[M]||{})[i];C&&(t=M);for(E in t)N=!n&&r&&E in r,N&&E in Q||(o=N?r[E]:t[E],Q[E]=C&&"function"!=typeof r[E]?t[E]:a&&N?e(o,I):B&&r[E]==o?function(A){var M=function(M){return this instanceof A?new A(M):A(M)};return M[i]=A[i],M}(o):D&&"function"==typeof o?e(Function.call,o):o,D&&((Q[i]||(Q[i]={}))[E]=o))};T.F=1,T.G=2,T.S=4,T.P=8,T.B=16,T.W=32,A.exports=T},function(A,M){var t=Object;A.exports={create:t.create,getProto:t.getPrototypeOf,isEnum:{}.propertyIsEnumerable,getDesc:t.getOwnPropertyDescriptor,setDesc:t.defineProperty,setDescs:t.defineProperties,getKeys:t.keys,getNames:t.getOwnPropertyNames,getSymbols:t.getOwnPropertySymbols,each:[].forEach}},function(A,M,t){"use strict";var I=t(32),g=function(){};I&&(g=function(){return document.addEventListener?function(A,M,t,I){return A.addEventListener(M,t,I||!1)}:document.attachEvent?function(A,M,t){return A.attachEvent("on"+M,t)}:void 0}()),A.exports=g},function(A,M,t){"use strict";var I=t(143),g=t(294),e=t(289),i=t(290),T=Object.prototype.hasOwnProperty;A.exports=function(A,M,t){var E="",N=M;if("string"==typeof M){if(void 0===t)return A.style[I(M)]||e(A).getPropertyValue(g(M));(N={})[M]=t}for(var o in N)T.call(N,o)&&(N[o]||0===N[o]?E+=g(o)+":"+N[o]+";":i(A,g(o)));A.style.cssText+=";"+E}},function(A,M,t){(function(){var t=this,I=t.humanize,g={};"undefined"!=typeof A&&A.exports&&(M=A.exports=g),M.humanize=g,g.noConflict=function(){return t.humanize=I,this},g.pad=function(A,M,t,I){if(A+="",t?t.length>1&&(t=t.charAt(0)):t=" ",I=void 0===I?"left":"right","right"===I)for(;A.length4&&A<21?"th":{1:"st",2:"nd",3:"rd"}[A%10]||"th"},w:function(){return t.getDay()},z:function(){return(o.L()?i[o.n()]:e[o.n()])+o.j()-1},W:function(){var A=o.z()-o.N()+1.5;return g.pad(1+Math.floor(Math.abs(A)/7)+(A%7>3.5?1:0),2,"0")},F:function(){return N[t.getMonth()]},m:function(){return g.pad(o.n(),2,"0")},M:function(){return o.F().slice(0,3)},n:function(){return t.getMonth()+1},t:function(){return new Date(o.Y(),o.n(),0).getDate()},L:function(){return 1===new Date(o.Y(),1,29).getMonth()?1:0},o:function(){var A=o.n(),M=o.W();return o.Y()+(12===A&&M<9?-1:1===A&&M>9)},Y:function(){return t.getFullYear()},y:function(){return String(o.Y()).slice(-2)},a:function(){return t.getHours()>11?"pm":"am"},A:function(){return o.a().toUpperCase()},B:function(){var A=t.getTime()/1e3,M=A%86400+3600;M<0&&(M+=86400);var I=M/86.4%1e3;return A<0?Math.ceil(I):Math.floor(I)},g:function(){return o.G()%12||12},G:function(){return t.getHours()},h:function(){return g.pad(o.g(),2,"0")},H:function(){return g.pad(o.G(),2,"0")},i:function(){return g.pad(t.getMinutes(),2,"0")},s:function(){return g.pad(t.getSeconds(),2,"0")},u:function(){return g.pad(1e3*t.getMilliseconds(),6,"0")},O:function(){var A=t.getTimezoneOffset(),M=Math.abs(A);return(A>0?"-":"+")+g.pad(100*Math.floor(M/60)+M%60,4,"0")},P:function(){var A=o.O();return A.substr(0,3)+":"+A.substr(3,2)},Z:function(){return 60*-t.getTimezoneOffset()},c:function(){return"Y-m-d\\TH:i:sP".replace(I,T)},r:function(){return"D, d M Y H:i:s O".replace(I,T)},U:function(){return t.getTime()/1e3||0}};return A.replace(I,T)},g.numberFormat=function(A,M,t,I){M=isNaN(M)?2:Math.abs(M),t=void 0===t?".":t,I=void 0===I?",":I;var g=A<0?"-":"";A=Math.abs(+A||0);var e=parseInt(A.toFixed(M),10)+"",i=e.length>3?e.length%3:0;return g+(i?e.substr(0,i)+I:"")+e.substr(i).replace(/(\d{3})(?=\d)/g,"$1"+I)+(M?t+Math.abs(A-e).toFixed(M).slice(2):"")},g.naturalDay=function(A,M){A=void 0===A?g.time():A,M=void 0===M?"Y-m-d":M;var t=86400,I=new Date,e=new Date(I.getFullYear(),I.getMonth(),I.getDate()).getTime()/1e3;return A=e-t?"yesterday":A>=e&&A=e+t&&A-2)return(t>=0?"just ":"")+"now";if(t<60&&t>-60)return t>=0?Math.floor(t)+" seconds ago":"in "+Math.floor(-t)+" seconds";if(t<120&&t>-120)return t>=0?"about a minute ago":"in about a minute";if(t<3600&&t>-3600)return t>=0?Math.floor(t/60)+" minutes ago":"in "+Math.floor(-t/60)+" minutes";if(t<7200&&t>-7200)return t>=0?"about an hour ago":"in about an hour";if(t<86400&&t>-86400)return t>=0?Math.floor(t/3600)+" hours ago":"in "+Math.floor(-t/3600)+" hours";var I=172800;if(t-I)return t>=0?"1 day ago":"in 1 day";var e=2505600;if(t-e)return t>=0?Math.floor(t/86400)+" days ago":"in "+Math.floor(-t/86400)+" days";var i=5184e3;if(t-i)return t>=0?"about a month ago":"in about a month";var T=parseInt(g.date("Y",M),10),E=parseInt(g.date("Y",A),10),N=12*T+parseInt(g.date("n",M),10),o=12*E+parseInt(g.date("n",A),10),n=N-o;if(n<12&&n>-12)return n>=0?n+" months ago":"in "+-n+" months";var C=T-E;return C<2&&C>-2?C>=0?"a year ago":"in a year":C>=0?C+" years ago":"in "+-C+" years"},g.ordinal=function(A){A=parseInt(A,10),A=isNaN(A)?0:A;var M=A<0?"-":"";A=Math.abs(A);var t=A%100;return M+A+(t>4&&t<21?"th":{1:"st",2:"nd",3:"rd"}[A%10]||"th")},g.filesize=function(A,M,t,I,e,i){return M=void 0===M?1024:M,A<=0?"0 bytes":(A

    "),A=A.replace(/\n/g,"
    "),"

    "+A+"

    "},g.nl2br=function(A){return A.replace(/(\r\n|\n|\r)/g,"
    ")},g.truncatechars=function(A,M){return A.length<=M?A:A.substr(0,M)+"…"},g.truncatewords=function(A,M){var t=A.split(" ");return t.length0,Q=C.enumErrorProps&&(A===u||A instanceof Error),r=C.enumPrototypes&&T(A);++IM.documentElement.clientHeight;return{modalStyles:{paddingRight:I&&!g?Q.default():void 0,paddingLeft:!I&&g?Q.default():void 0}}}});v.Body=z.default,v.Header=U.default,v.Title=m.default,v.Footer=f.default,v.Dialog=h.default,v.TRANSITION_DURATION=300,v.BACKDROP_TRANSITION_DURATION=150,M.default=c.bsSizes([a.Sizes.LARGE,a.Sizes.SMALL],c.bsClass("modal",v)),A.exports=M.default},function(A,M,t){"use strict";var I=t(15).default,g=t(14).default,e=t(38).default,i=t(7).default,T=t(6).default; +M.__esModule=!0;var E=t(1),N=T(E),o=t(5),n=T(o),C=t(11),c=T(C),D=t(44),a=T(D),B=function(A){function M(){g(this,M),A.apply(this,arguments)}return I(M,A),M.prototype.render=function(){var A=this.props,M=A["aria-label"],t=e(A,["aria-label"]),I=a.default(this.context.$bs_onModalHide,this.props.onHide);return N.default.createElement("div",i({},t,{className:n.default(this.props.className,c.default.prefix(this.props,"header"))}),this.props.closeButton&&N.default.createElement("button",{type:"button",className:"close","aria-label":M,onClick:I},N.default.createElement("span",{"aria-hidden":"true"},"×")),this.props.children)},M}(N.default.Component);B.propTypes={"aria-label":N.default.PropTypes.string,bsClass:N.default.PropTypes.string,closeButton:N.default.PropTypes.bool,onHide:N.default.PropTypes.func},B.contextTypes={$bs_onModalHide:N.default.PropTypes.func},B.defaultProps={"aria-label":"Close",closeButton:!1},M.default=C.bsClass("modal",B),A.exports=M.default},function(A,M,t){"use strict";function I(A,M){return Array.isArray(M)?M.indexOf(A)>=0:A===M}var g=t(7).default,e=t(78).default,i=t(6).default;M.__esModule=!0;var T=t(40),E=i(T),N=t(168),o=i(N),n=t(1),C=i(n),c=t(16),D=i(c),a=t(127),B=(i(a),t(369)),Q=i(B),r=t(44),s=i(r),x=C.default.createClass({displayName:"OverlayTrigger",propTypes:g({},Q.default.propTypes,{trigger:C.default.PropTypes.oneOfType([C.default.PropTypes.oneOf(["click","hover","focus"]),C.default.PropTypes.arrayOf(C.default.PropTypes.oneOf(["click","hover","focus"]))]),delay:C.default.PropTypes.number,delayShow:C.default.PropTypes.number,delayHide:C.default.PropTypes.number,defaultOverlayShown:C.default.PropTypes.bool,overlay:C.default.PropTypes.node.isRequired,onBlur:C.default.PropTypes.func,onClick:C.default.PropTypes.func,onFocus:C.default.PropTypes.func,onMouseEnter:C.default.PropTypes.func,onMouseLeave:C.default.PropTypes.func,target:function(){},onHide:function(){},show:function(){}}),getDefaultProps:function(){return{defaultOverlayShown:!1,trigger:["hover","focus"]}},getInitialState:function(){return{isOverlayShown:this.props.defaultOverlayShown}},show:function(){this.setState({isOverlayShown:!0})},hide:function(){this.setState({isOverlayShown:!1})},toggle:function(){this.state.isOverlayShown?this.hide():this.show()},componentWillMount:function(){this.handleMouseOver=this.handleMouseOverOut.bind(null,this.handleDelayedShow),this.handleMouseOut=this.handleMouseOverOut.bind(null,this.handleDelayedHide)},componentDidMount:function(){this._mountNode=document.createElement("div"),this.renderOverlay()},renderOverlay:function(){D.default.unstable_renderSubtreeIntoContainer(this,this._overlay,this._mountNode)},componentWillUnmount:function(){D.default.unmountComponentAtNode(this._mountNode),this._mountNode=null,clearTimeout(this._hoverShowDelay),clearTimeout(this._hoverHideDelay)},componentDidUpdate:function(){this._mountNode&&this.renderOverlay()},getOverlayTarget:function(){return D.default.findDOMNode(this)},getOverlay:function(){var A=g({},o.default(this.props,e(Q.default.propTypes)),{show:this.state.isOverlayShown,onHide:this.hide,target:this.getOverlayTarget,onExit:this.props.onExit,onExiting:this.props.onExiting,onExited:this.props.onExited,onEnter:this.props.onEnter,onEntering:this.props.onEntering,onEntered:this.props.onEntered}),M=n.cloneElement(this.props.overlay,{placement:A.placement,container:A.container});return C.default.createElement(Q.default,A,M)},render:function(){var A=C.default.Children.only(this.props.children),M=A.props,t={"aria-describedby":this.props.overlay.props.id};return this._overlay=this.getOverlay(),t.onClick=s.default(M.onClick,this.props.onClick),I("click",this.props.trigger)&&(t.onClick=s.default(this.toggle,t.onClick)),I("hover",this.props.trigger)&&(t.onMouseOver=s.default(this.handleMouseOver,this.props.onMouseOver,M.onMouseOver),t.onMouseOut=s.default(this.handleMouseOut,this.props.onMouseOut,M.onMouseOut)),I("focus",this.props.trigger)&&(t.onFocus=s.default(this.handleDelayedShow,this.props.onFocus,M.onFocus),t.onBlur=s.default(this.handleDelayedHide,this.props.onBlur,M.onBlur)),n.cloneElement(A,t)},handleDelayedShow:function(){var A=this;if(null!=this._hoverHideDelay)return clearTimeout(this._hoverHideDelay),void(this._hoverHideDelay=null);if(!this.state.isOverlayShown&&null==this._hoverShowDelay){var M=null!=this.props.delayShow?this.props.delayShow:this.props.delay;return M?void(this._hoverShowDelay=setTimeout(function(){A._hoverShowDelay=null,A.show()},M)):void this.show()}},handleDelayedHide:function(){var A=this;if(null!=this._hoverShowDelay)return clearTimeout(this._hoverShowDelay),void(this._hoverShowDelay=null);if(this.state.isOverlayShown&&null==this._hoverHideDelay){var M=null!=this.props.delayHide?this.props.delayHide:this.props.delay;return M?void(this._hoverHideDelay=setTimeout(function(){A._hoverHideDelay=null,A.hide()},M)):void this.hide()}},handleMouseOverOut:function(A,M){var t=M.currentTarget,I=M.relatedTarget||M.nativeEvent.toElement;I&&(I===t||E.default(t,I))||A(M)}});M.default=x,A.exports=M.default},function(A,M,t){"use strict";var I=t(7).default,g=t(6).default;M.__esModule=!0;var e=t(1),i=g(e),T=t(5),E=g(T),N=t(11),o=g(N),n=t(181),C=g(n),c=i.default.createClass({displayName:"Tooltip",propTypes:{id:C.default(i.default.PropTypes.oneOfType([i.default.PropTypes.string,i.default.PropTypes.number])),placement:i.default.PropTypes.oneOf(["top","right","bottom","left"]),positionLeft:i.default.PropTypes.number,positionTop:i.default.PropTypes.number,arrowOffsetLeft:i.default.PropTypes.oneOfType([i.default.PropTypes.number,i.default.PropTypes.string]),arrowOffsetTop:i.default.PropTypes.oneOfType([i.default.PropTypes.number,i.default.PropTypes.string]),title:i.default.PropTypes.node},getDefaultProps:function(){return{bsClass:"tooltip",placement:"right"}},render:function(){var A,M=(A={},A[o.default.prefix(this.props)]=!0,A[this.props.placement]=!0,A),t=I({left:this.props.positionLeft,top:this.props.positionTop},this.props.style),g={left:this.props.arrowOffsetLeft,top:this.props.arrowOffsetTop};return i.default.createElement("div",I({role:"tooltip"},this.props,{className:E.default(this.props.className,M),style:t}),i.default.createElement("div",{className:o.default.prefix(this.props,"arrow"),style:g}),i.default.createElement("div",{className:o.default.prefix(this.props,"inner")},this.props.children))}});M.default=c,A.exports=M.default},function(A,M,t){"use strict";function I(A){return A&&A.__esModule?A:{default:A}}function g(A,M){return A="function"==typeof A?A():A,i.default.findDOMNode(A)||M}Object.defineProperty(M,"__esModule",{value:!0}),M.default=g;var e=t(16),i=I(e);A.exports=M.default},function(A,M,t){"use strict";function I(A){return A&&A.__esModule?A:{default:A}}function g(A,M,t,I,g){var i=A[M],E="undefined"==typeof i?"undefined":e(i);return T.default.isValidElement(i)?new Error("Invalid "+I+" ` + "`" + `"+g+"` + "`" + ` of type ReactElement "+("supplied to ` + "`" + `"+t+"` + "`" + `, expected a ReactComponent or a ")+"DOMElement. You can usually obtain a ReactComponent or DOMElement from a ReactElement by attaching a ref to it."):"object"===E&&"function"==typeof i.render||1===i.nodeType?null:new Error("Invalid "+I+" ` + "`" + `"+g+"` + "`" + ` of value ` + "`" + `"+i+"` + "`" + ` "+("supplied to ` + "`" + `"+t+"` + "`" + `, expected a ReactComponent or a ")+"DOMElement.")}M.__esModule=!0;var e="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(A){return typeof A}:function(A){return A&&"function"==typeof Symbol&&A.constructor===Symbol?"symbol":typeof A},i=t(1),T=I(i),E=t(180),N=I(E);M.default=(0,N.default)(g)},function(A,M,t){"use strict";function I(){function A(A,M,I){for(var g=0;g>",null!=t[I]?A(t,I,g):M?new Error("Required prop '"+I+"' was not specified in '"+g+"'."):void 0}var t=M.bind(null,!1);return t.isRequired=M.bind(null,!0),t}M.__esModule=!0,M.errMsg=t,M.createChainableTypeChecker=I},function(A,M){"use strict";function t(A,M,t){function I(){return i=!0,T?void(N=[].concat(Array.prototype.slice.call(arguments))):void t.apply(this,arguments)}function g(){if(!i&&(E=!0,!T)){for(T=!0;!i&&e=A&&E&&(i=!0,t()))}}var e=0,i=!1,T=!1,E=!1,N=void 0;g()}function I(A,M,t){function I(A,M,I){i||(M?(i=!0,t(M)):(e[A]=I,i=++T===g,i&&t(null,e)))}var g=A.length,e=[];if(0===g)return t(null,e);var i=!1,T=0;A.forEach(function(A,t){M(A,t,function(A,M){I(t,A,M)})})}M.__esModule=!0,M.loopAsync=t,M.mapAsync=I},function(A,M,t){"use strict";function I(A){if(A&&A.__esModule)return A;var M={};if(null!=A)for(var t in A)Object.prototype.hasOwnProperty.call(A,t)&&(M[t]=A[t]);return M.default=A,M}function g(A){return A&&A.__esModule?A:{default:A}}M.__esModule=!0,M.router=M.routes=M.route=M.components=M.component=M.location=M.history=M.falsy=M.locationShape=M.routerShape=void 0;var e=t(1),i=t(66),T=(g(i),t(34)),E=I(T),N=t(9),o=(g(N),e.PropTypes.func),n=e.PropTypes.object,C=e.PropTypes.shape,c=e.PropTypes.string,D=M.routerShape=C({push:o.isRequired,replace:o.isRequired,go:o.isRequired,goBack:o.isRequired,goForward:o.isRequired,setRouteLeaveHook:o.isRequired,isActive:o.isRequired}),a=M.locationShape=C({pathname:c.isRequired,search:c.isRequired,state:n,action:c.isRequired,key:c}),B=M.falsy=E.falsy,Q=M.history=E.history,r=M.location=a,s=M.component=E.component,x=M.components=E.components,j=M.route=E.route,y=(M.routes=E.routes,M.router=D),u={falsy:B,history:Q,location:r,component:s,components:x,route:j,router:y};M.default=u},function(A,M,t){"use strict";function I(A){return A&&A.__esModule?A:{default:A}}function g(A){for(var M in A)if(Object.prototype.hasOwnProperty.call(A,M))return!0;return!1}function e(A,M){function t(M){var t=!(arguments.length<=1||void 0===arguments[1])&&arguments[1],I=arguments.length<=2||void 0===arguments[2]?null:arguments[2],g=void 0;return t&&t!==!0||null!==I?(M={pathname:M,query:t},g=I||!1):(M=A.createLocation(M),g=t),(0,C.default)(M,g,s.location,s.routes,s.params)}function I(A,t){x&&x.location===A?e(x,t):(0,B.default)(M,A,function(M,I){M?t(M):I?e(i({},I,{location:A}),t):t()})}function e(A,M){function t(t,g){return t||g?I(t,g):void(0,D.default)(A,function(t,I){t?M(t):M(null,null,s=i({},A,{components:I}))})}function I(A,t){A?M(A):M(null,t)}var g=(0,N.default)(s,A),e=g.leaveRoutes,T=g.changeRoutes,E=g.enterRoutes;(0,o.runLeaveHooks)(e,s),e.filter(function(A){return E.indexOf(A)===-1}).forEach(a),(0,o.runChangeHooks)(T,s,A,function(M,g){return M||g?I(M,g):void(0,o.runEnterHooks)(E,A,t)})}function T(A){var M=arguments.length<=1||void 0===arguments[1]||arguments[1];return A.__id__||M&&(A.__id__=j++)}function E(A){return A.reduce(function(A,M){return A.push.apply(A,y[T(M)]),A},[])}function n(A,t){(0,B.default)(M,A,function(M,I){if(null==I)return void t();x=i({},I,{location:A});for(var g=E((0,N.default)(s,x).leaveRoutes),e=void 0,T=0,o=g.length;null==e&&T=32||13===M?M:0}A.exports=t},function(A,M){"use strict";function t(A){var M=this,t=M.nativeEvent;if(t.getModifierState)return t.getModifierState(A);var I=g[A];return!!I&&!!t[I]}function I(A){return t}var g={Alt:"altKey",Control:"ctrlKey",Meta:"metaKey",Shift:"shiftKey"};A.exports=I},function(A,M){"use strict";function t(A){var M=A.target||A.srcElement||window;return 3===M.nodeType?M.parentNode:M}A.exports=t},function(A,M){"use strict";function t(A){var M=A&&(I&&A[I]||A[g]);if("function"==typeof M)return M}var I="function"==typeof Symbol&&Symbol.iterator,g="@@iterator";A.exports=t},function(A,M,t){"use strict";function I(A){return"function"==typeof A&&"undefined"!=typeof A.prototype&&"function"==typeof A.prototype.mountComponent&&"function"==typeof A.prototype.receiveComponent}function g(A){var M;if(null===A||A===!1)M=new i(g);else if("object"==typeof A){var t=A;!t||"function"!=typeof t.type&&"string"!=typeof t.type?N(!1):void 0,M="string"==typeof t.type?T.createInternalComponent(t):I(t.type)?new t.type(t):new o}else"string"==typeof A||"number"==typeof A?M=T.createInstanceForText(A):N(!1);return M.construct(A),M._mountIndex=0,M._mountImage=null,M}var e=t(442),i=t(213),T=t(219),E=t(4),N=t(2),o=(t(3),function(){});E(o.prototype,e.Mixin,{_instantiateReactComponent:g}),A.exports=g},function(A,M,t){"use strict";/** * Checks if an event is supported in the current execution environment. * * NOTE: This will not work correctly for non-generic events such as ` + "`" + `change` + "`" + `, @@ -200,42 +200,42 @@ A.exports=function(A){return A===A.window?A:9===A.nodeType&&(A.defaultView||A.pa * @internal * @license Modernizr 3.0.0pre (Custom Build) | MIT */ -function I(A,M){if(!e.canUseDOM||M&&!("addEventListener"in document))return!1;var t="on"+A,I=t in document;if(!I){var i=document.createElement("div");i.setAttribute(t,"return;"),I="function"==typeof i[t]}return!I&&g&&"wheel"===A&&(I=document.implementation.hasFeature("Events.wheel","3.0")),I}var g,e=t(10);e.canUseDOM&&(g=document.implementation&&document.implementation.hasFeature&&document.implementation.hasFeature("","")!==!0),A.exports=I},function(A,M,t){"use strict";var I=t(10),g=t(75),e=t(76),i=function(A,M){A.textContent=M};I.canUseDOM&&("textContent"in document.documentElement||(i=function(A,M){e(A,g(M))})),A.exports=i},function(A,M){"use strict";function t(A,M){var t=null===A||A===!1,I=null===M||M===!1;if(t||I)return t===I;var g=typeof A,e=typeof M;return"string"===g||"number"===g?"string"===e||"number"===e:"object"===e&&A.type===M.type&&A.key===M.key}A.exports=t},function(A,M,t){"use strict";function I(A){return a[A]}function g(A,M){return A&&null!=A.key?i(A.key):M.toString(36)}function e(A){return(""+A).replace(B,I)}function i(A){return"$"+e(A)}function T(A,M,t,I){var e=typeof A;if("undefined"!==e&&"boolean"!==e||(A=null),null===A||"string"===e||"number"===e||N.isValidElement(A))return t(I,A,""===M?c+g(A,0):M),1;var E,o,a=0,B=""===M?c:M+D;if(Array.isArray(A))for(var Q=0;QM.name.toLowerCase()?1:0}),g=g.sort(function(A,M){return A.name.toLowerCase()M.name.toLowerCase()?1:0}),M&&(t=t.reverse(),g=g.reverse()),[].concat(I(t),I(g))},M.sortObjectsBySize=function(A,M){var t=A.filter(function(A){return A.name.endsWith("/")}),g=A.filter(function(A){return!A.name.endsWith("/")});return g=g.sort(function(A,M){return A.size-M.size}),M&&(g=g.reverse()),[].concat(I(t),I(g))},M.sortObjectsByDate=function(A,M){var t=A.filter(function(A){return A.name.endsWith("/")}),g=A.filter(function(A){return!A.name.endsWith("/")});return g=g.sort(function(A,M){return new Date(A.lastModified).getTime()-new Date(M.lastModified).getTime()}),M&&(g=g.reverse()),[].concat(I(t),I(g))},M.pathSlice=function(A){A=A.replace(g.minioBrowserPrefix,"");var M="",t="";if(!A)return{bucket:t,prefix:M};var I=A.indexOf("/",1);return I==-1?(t=A.slice(1),{bucket:t,prefix:M}):(t=A.slice(1,I),M=A.slice(I+1),{bucket:t,prefix:M})},M.pathJoin=function(A,M){return M||(M=""),g.minioBrowserPrefix+"/"+A+"/"+M}},function(A,M,t){"use strict";function I(A){if(A&&A.__esModule)return A;var M={};if(null!=A)for(var t in A)Object.prototype.hasOwnProperty.call(A,t)&&(M[t]=A[t]);return M.default=A,M}function g(A){return A&&A.__esModule?A:{default:A}}function e(A,M){if(!(A instanceof M))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(M,"__esModule",{value:!0});var i=function(){function A(A,M){for(var t=0;t":i.innerHTML="<"+A+">",T[A]=!i.firstChild),T[A]?C[A]:null}var g=t(10),e=t(2),i=g.canUseDOM?document.createElement("div"):null,T={},E=[1,'"],N=[1,"","
    "],o=[3,"","
    "],n=[1,'',""],C={"*":[1,"?
    ","
    "],area:[1,"",""],col:[2,"","
    "],legend:[1,"
    ","
    "],param:[1,"",""],tr:[2,"","
    "],optgroup:E,option:E,caption:N,colgroup:N,tbody:N,tfoot:N,thead:N,td:o,th:o},c=["circle","clipPath","defs","ellipse","g","image","line","linearGradient","mask","path","pattern","polygon","polyline","radialGradient","rect","stop","text","tspan"];c.forEach(function(A){C[A]=n,T[A]=!0}),A.exports=I},function(A,M){"use strict";function t(A,M){if(A===M)return!0;if("object"!=typeof A||null===A||"object"!=typeof M||null===M)return!1;var t=Object.keys(A),g=Object.keys(M);if(t.length!==g.length)return!1;for(var e=I.bind(M),i=0;i":i.innerHTML="<"+A+">",T[A]=!i.firstChild),T[A]?C[A]:null}var g=t(10),e=t(2),i=g.canUseDOM?document.createElement("div"):null,T={},E=[1,'"],N=[1,"","
    "],o=[3,"","
    "],n=[1,'',""],C={"*":[1,"?
    ","
    "],area:[1,"",""],col:[2,"","
    "],legend:[1,"
    ","
    "],param:[1,"",""],tr:[2,"","
    "],optgroup:E,option:E,caption:N,colgroup:N,tbody:N,tfoot:N,thead:N,td:o,th:o},c=["circle","clipPath","defs","ellipse","g","image","line","linearGradient","mask","path","pattern","polygon","polyline","radialGradient","rect","stop","text","tspan"];c.forEach(function(A){C[A]=n,T[A]=!0}),A.exports=I},function(A,M){"use strict";function t(A,M){if(A===M)return!0;if("object"!=typeof A||null===A||"object"!=typeof M||null===M)return!1;var t=Object.keys(A),g=Object.keys(M);if(t.length!==g.length)return!1;for(var e=I.bind(M),i=0;i-1&&A%1==0&&A1)for(var t=1;tA.clientHeight}Object.defineProperty(M,"__esModule",{value:!0}),M.default=i;var T=t(57),E=I(T),N=t(39),o=I(N);A.exports=M.default},function(A,M,t){"use strict";function I(A){return A&&A.__esModule?A:{default:A}}function g(A,M,t,I,g){var i=A[M],E="undefined"==typeof i?"undefined":e(i);return T.default.isValidElement(i)?new Error("Invalid "+I+" ` + "`" + `"+g+"` + "`" + ` of type ReactElement "+("supplied to ` + "`" + `"+t+"` + "`" + `, expected an element type (a string ")+"or a ReactClass)."):"function"!==E&&"string"!==E?new Error("Invalid "+I+" ` + "`" + `"+g+"` + "`" + ` of value ` + "`" + `"+i+"` + "`" + ` "+("supplied to ` + "`" + `"+t+"` + "`" + `, expected an element type (a string ")+"or a ReactClass)."):null}M.__esModule=!0;var e="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(A){return typeof A}:function(A){return A&&"function"==typeof Symbol&&A.constructor===Symbol?"symbol":typeof A},i=t(1),T=I(i),E=t(182),N=I(E);M.default=(0,N.default)(g)},function(A,M){"use strict";function t(A){function M(M,t,I,g,e,i){var T=g||"<>",E=i||I;if(null==t[I])return M?new Error("Required "+e+" ` + "`" + `"+E+"` + "`" + ` was not specified "+("in ` + "`" + `"+T+"` + "`" + `.")):null;for(var N=arguments.length,o=Array(N>6?N-6:0),n=6;n=0||Object.prototype.hasOwnProperty.call(A,I)&&(t[I]=A[I]);return t}function e(A){return 0===A.button}function i(A){return!!(A.metaKey||A.altKey||A.ctrlKey||A.shiftKey)}function T(A){for(var M in A)if(Object.prototype.hasOwnProperty.call(A,M))return!1;return!0}function E(A,M){var t=M.query,I=M.hash,g=M.state;return t||I||g?{pathname:A,query:t,hash:I,state:g}:A}M.__esModule=!0;var N=Object.assign||function(A){for(var M=1;M=0;I--){var g=A[I],e=g.path||"";if(t=e.replace(/\/*$/,"/")+t,0===e.indexOf("/"))break}return"/"+t}},propTypes:{path:C,from:C,to:C.isRequired,query:c,state:c,onEnter:o.falsy,children:o.falsy},render:function(){(0,T.default)(!1)}});M.default=D,A.exports=M.default},function(A,M,t){"use strict";function I(A){return A&&A.__esModule?A:{default:A}}M.__esModule=!0;var g=t(1),e=I(g),i=t(8),T=I(i),E=t(30),N=t(34),o=e.default.PropTypes,n=o.string,C=o.func,c=e.default.createClass({displayName:"Route",statics:{createRouteFromReactElement:E.createRouteFromReactElement},propTypes:{path:n,component:N.component,components:N.components,getComponent:C,getComponents:C},render:function(){(0,T.default)(!1)}});M.default=c,A.exports=M.default},function(A,M,t){"use strict";function I(A){return A&&A.__esModule?A:{default:A}}function g(A,M){var t={};for(var I in A)M.indexOf(I)>=0||Object.prototype.hasOwnProperty.call(A,I)&&(t[I]=A[I]);return t}function e(A){return!A||!A.__v2_compatible__}function i(A){return A&&A.getCurrentLocation}M.__esModule=!0;var T=Object.assign||function(A){for(var M=1;M=0&&0===window.sessionStorage.length)return;throw A}}function i(A){var M=void 0;try{M=window.sessionStorage.getItem(g(A))}catch(A){if(A.name===o)return null}if(M)try{return JSON.parse(M)}catch(A){}return null}M.__esModule=!0,M.saveState=e,M.readState=i;var T=t(23),E=(I(T),"@@History/"),N=["QuotaExceededError","QUOTA_EXCEEDED_ERR"],o="SecurityError"},function(A,M,t){"use strict";function I(A){return A&&A.__esModule?A:{default:A}}function g(A){function M(A){return E.canUseDOM?void 0:T.default(!1),t.listen(A)}var t=n.default(e({getUserConfirmation:N.getUserConfirmation},A,{go:N.go}));return e({},t,{listen:M})}M.__esModule=!0;var e=Object.assign||function(A){for(var M=1;M1?M-1:0),e=1;e=A.childNodes.length?null:A.childNodes.item(t);A.insertBefore(M,I)}var g=t(434),e=t(220),i=t(17),T=t(76),E=t(121),N=t(2),o={dangerouslyReplaceNodeWithMarkup:g.dangerouslyReplaceNodeWithMarkup,updateTextContent:E,processUpdates:function(A,M){for(var t,i=null,o=null,n=0;n-1?void 0:i(!1),!N.plugins[t]){M.extractEvents?void 0:i(!1),N.plugins[t]=M;var I=M.eventTypes;for(var e in I)g(I[e],M,e)?void 0:i(!1)}}}function g(A,M,t){N.eventNameDispatchConfigs.hasOwnProperty(t)?i(!1):void 0,N.eventNameDispatchConfigs[t]=A;var I=A.phasedRegistrationNames;if(I){for(var g in I)if(I.hasOwnProperty(g)){var T=I[g];e(T,M,t)}return!0}return!!A.registrationName&&(e(A.registrationName,M,t),!0)}function e(A,M,t){N.registrationNameModules[A]?i(!1):void 0,N.registrationNameModules[A]=M,N.registrationNameDependencies[A]=M.eventTypes[t].dependencies}var i=t(2),T=null,E={},N={plugins:[],eventNameDispatchConfigs:{},registrationNameModules:{},registrationNameDependencies:{},injectEventPluginOrder:function(A){T?i(!1):void 0,T=Array.prototype.slice.call(A),I()},injectEventPluginsByName:function(A){var M=!1;for(var t in A)if(A.hasOwnProperty(t)){var g=A[t];E.hasOwnProperty(t)&&E[t]===g||(E[t]?i(!1):void 0,E[t]=g,M=!0)}M&&I()},getPluginModuleForEvent:function(A){var M=A.dispatchConfig;if(M.registrationName)return N.registrationNameModules[M.registrationName]||null;for(var t in M.phasedRegistrationNames)if(M.phasedRegistrationNames.hasOwnProperty(t)){var I=N.registrationNameModules[M.phasedRegistrationNames[t]];if(I)return I}return null},_resetEventPlugins:function(){T=null;for(var A in E)E.hasOwnProperty(A)&&delete E[A];N.plugins.length=0;var M=N.eventNameDispatchConfigs;for(var t in M)M.hasOwnProperty(t)&&delete M[t];var I=N.registrationNameModules;for(var g in I)I.hasOwnProperty(g)&&delete I[g]}};A.exports=N},function(A,M,t){"use strict";function I(A){return(""+A).replace(x,"//")}function g(A,M){this.func=A,this.context=M,this.count=0}function e(A,M,t){var I=A.func,g=A.context;I.call(g,M,A.count++)}function i(A,M,t){if(null==A)return A;var I=g.getPooled(M,t);Q(A,e,I),g.release(I)}function T(A,M,t,I){this.result=A,this.keyPrefix=M,this.func=t,this.context=I,this.count=0}function E(A,M,t){var g=A.result,e=A.keyPrefix,i=A.func,T=A.context,E=i.call(T,M,A.count++);Array.isArray(E)?N(E,g,t,B.thatReturnsArgument):null!=E&&(a.isValidElement(E)&&(E=a.cloneAndReplaceKey(E,e+(E!==M?I(E.key||"")+"/":"")+t)),g.push(E))}function N(A,M,t,g,e){var i="";null!=t&&(i=I(t)+"/");var N=T.getPooled(M,i,g,e);Q(A,E,N),T.release(N)}function o(A,M,t){if(null==A)return A;var I=[];return N(A,I,null,M,t),I}function n(A,M,t){return null}function C(A,M){return Q(A,n,null)}function c(A){var M=[];return N(A,M,null,B.thatReturnsArgument),M}var D=t(31),a=t(13),B=t(20),Q=t(123),r=D.twoArgumentPooler,s=D.fourArgumentPooler,x=/\/(?!\/)/g;g.prototype.destructor=function(){this.func=null,this.context=null,this.count=0},D.addPoolingTo(g,r),T.prototype.destructor=function(){this.result=null,this.keyPrefix=null,this.func=null,this.context=null,this.count=0},D.addPoolingTo(T,s);var j={forEach:i,map:o,mapIntoWithKeyPrefixInternal:N,count:C,toArray:c};A.exports=j},function(A,M,t){"use strict";function I(A,M){var t=y.hasOwnProperty(M)?y[M]:null;w.hasOwnProperty(M)&&(t!==x.OVERRIDE_BASE?B(!1):void 0),A.hasOwnProperty(M)&&(t!==x.DEFINE_MANY&&t!==x.DEFINE_MANY_MERGED?B(!1):void 0)}function g(A,M){if(M){"function"==typeof M?B(!1):void 0,C.isValidElement(M)?B(!1):void 0;var t=A.prototype;M.hasOwnProperty(s)&&u.mixins(A,M.mixins);for(var g in M)if(M.hasOwnProperty(g)&&g!==s){var e=M[g];if(I(t,g),u.hasOwnProperty(g))u[g](A,e);else{var i=y.hasOwnProperty(g),N=t.hasOwnProperty(g),o="function"==typeof e,n=o&&!i&&!N&&M.autobind!==!1;if(n)t.__reactAutoBindMap||(t.__reactAutoBindMap={}),t.__reactAutoBindMap[g]=e,t[g]=e;else if(N){var c=y[g];!i||c!==x.DEFINE_MANY_MERGED&&c!==x.DEFINE_MANY?B(!1):void 0,c===x.DEFINE_MANY_MERGED?t[g]=T(t[g],e):c===x.DEFINE_MANY&&(t[g]=E(t[g],e))}else t[g]=e}}}}function e(A,M){if(M)for(var t in M){var I=M[t];if(M.hasOwnProperty(t)){var g=t in u;g?B(!1):void 0;var e=t in A;e?B(!1):void 0,A[t]=I}}}function i(A,M){A&&M&&"object"==typeof A&&"object"==typeof M?void 0:B(!1);for(var t in M)M.hasOwnProperty(t)&&(void 0!==A[t]?B(!1):void 0,A[t]=M[t]);return A}function T(A,M){return function(){var t=A.apply(this,arguments),I=M.apply(this,arguments);if(null==t)return I;if(null==I)return t;var g={};return i(g,t),i(g,I),g}}function E(A,M){return function(){A.apply(this,arguments),M.apply(this,arguments)}}function N(A,M){var t=M.bind(A);return t}function o(A){for(var M in A.__reactAutoBindMap)if(A.__reactAutoBindMap.hasOwnProperty(M)){var t=A.__reactAutoBindMap[M];A[M]=N(A,t)}}var n=t(207),C=t(13),c=(t(71),t(70),t(222)),D=t(4),a=t(50),B=t(2),Q=t(59),r=t(27),s=(t(3),r({mixins:null})),x=Q({DEFINE_ONCE:null,DEFINE_MANY:null,OVERRIDE_BASE:null,DEFINE_MANY_MERGED:null}),j=[],y={mixins:x.DEFINE_MANY,statics:x.DEFINE_MANY,propTypes:x.DEFINE_MANY,contextTypes:x.DEFINE_MANY,childContextTypes:x.DEFINE_MANY,getDefaultProps:x.DEFINE_MANY_MERGED,getInitialState:x.DEFINE_MANY_MERGED,getChildContext:x.DEFINE_MANY_MERGED,render:x.DEFINE_ONCE,componentWillMount:x.DEFINE_MANY,componentDidMount:x.DEFINE_MANY,componentWillReceiveProps:x.DEFINE_MANY,shouldComponentUpdate:x.DEFINE_ONCE,componentWillUpdate:x.DEFINE_MANY,componentDidUpdate:x.DEFINE_MANY,componentWillUnmount:x.DEFINE_MANY,updateComponent:x.OVERRIDE_BASE},u={displayName:function(A,M){A.displayName=M},mixins:function(A,M){if(M)for(var t=0;t"+T+""},receiveComponent:function(A,M){if(A!==this._currentElement){this._currentElement=A;var t=""+A;if(t!==this._stringText){this._stringText=t;var g=i.getNode(this._rootNodeID);I.updateTextContent(g,t)}}},unmountComponent:function(){e.unmountIDFromEnvironment(this._rootNodeID)}}),A.exports=o},function(A,M,t){"use strict";function I(){this.reinitializeTransaction()}var g=t(18),e=t(73),i=t(4),T=t(20),E={initialize:T,close:function(){C.isBatchingUpdates=!1}},N={initialize:T,close:g.flushBatchedUpdates.bind(g)},o=[N,E];i(I.prototype,e.Mixin,{getTransactionWrappers:function(){return o}});var n=new I,C={isBatchingUpdates:!1,batchedUpdates:function(A,M,t,I,g,e){var i=C.isBatchingUpdates;C.isBatchingUpdates=!0,i?A(M,t,I,g,e):n.perform(A,null,M,t,I,g,e)}};A.exports=C},function(A,M,t){"use strict";function I(){if(!L){L=!0,Q.EventEmitter.injectReactEventListener(B),Q.EventPluginHub.injectEventPluginOrder(T),Q.EventPluginHub.injectInstanceHandle(r),Q.EventPluginHub.injectMount(s),Q.EventPluginHub.injectEventPluginsByName({SimpleEventPlugin:u,EnterLeaveEventPlugin:E,ChangeEventPlugin:e,SelectEventPlugin:j,BeforeInputEventPlugin:g}),Q.NativeComponent.injectGenericComponentClass(D),Q.NativeComponent.injectTextComponentClass(a),Q.Class.injectMixin(n),Q.DOMProperty.injectDOMPropertyConfig(o),Q.DOMProperty.injectDOMPropertyConfig(w),Q.EmptyComponent.injectEmptyComponent("noscript"),Q.Updates.injectReconcileTransaction(x),Q.Updates.injectBatchingStrategy(c),Q.RootIndex.injectCreateReactRootIndex(N.canUseDOM?i.createReactRootIndex:y.createReactRootIndex),Q.Component.injectEnvironment(C)}}var g=t(430),e=t(432),i=t(433),T=t(435),E=t(436),N=t(10),o=t(439),n=t(441),C=t(109),c=t(212),D=t(445),a=t(211),B=t(453),Q=t(454),r=t(48),s=t(12),x=t(458),j=t(464),y=t(465),u=t(466),w=t(463),L=!1;A.exports={inject:I}},function(A,M,t){"use strict";function I(){if(n.current){var A=n.current.getName();if(A)return" Check the render method of ` + "`" + `"+A+"` + "`" + `."}return""}function g(A,M){if(A._store&&!A._store.validated&&null==A.key){A._store.validated=!0;e("uniqueKey",A,M)}}function e(A,M,t){var g=I();if(!g){var e="string"==typeof t?t:t.displayName||t.name;e&&(g=" Check the top-level render call using <"+e+">.")}var i=D[A]||(D[A]={});if(i[g])return null;i[g]=!0;var T={parentOrOwner:g,url:" See https://fb.me/react-warning-keys for more information.",childOwner:null};return M&&M._owner&&M._owner!==n.current&&(T.childOwner=" It was passed a child from "+M._owner.getName()+"."),T}function i(A,M){if("object"==typeof A)if(Array.isArray(A))for(var t=0;t/,e={CHECKSUM_ATTR_NAME:"data-react-checksum",addChecksumToMarkup:function(A){var M=I(A);return A.replace(g," "+e.CHECKSUM_ATTR_NAME+'="'+M+'"$&')},canReuseMarkup:function(A,M){var t=M.getAttribute(e.CHECKSUM_ATTR_NAME);t=t&&parseInt(t,10);var g=I(A);return g===t}};A.exports=e},function(A,M,t){"use strict";var I=t(59),g=I({INSERT_MARKUP:null,MOVE_EXISTING:null,REMOVE_NODE:null,SET_MARKUP:null,TEXT_CONTENT:null});A.exports=g},function(A,M,t){"use strict";function I(A){if("function"==typeof A.type)return A.type;var M=A.type,t=n[M];return null==t&&(n[M]=t=N(M)),t}function g(A){return o?void 0:E(!1),new o(A.type,A.props)}function e(A){return new C(A)}function i(A){return A instanceof C}var T=t(4),E=t(2),N=null,o=null,n={},C=null,c={injectGenericComponentClass:function(A){o=A},injectTextComponentClass:function(A){C=A},injectComponentClasses:function(A){T(n,A)}},D={getComponentClassForElement:I,createInternalComponent:g,createInstanceForText:e,isTextComponent:i,injection:c};A.exports=D},function(A,M,t){"use strict";function I(A,M){}var g=(t(3),{isMounted:function(A){return!1},enqueueCallback:function(A,M){},enqueueForceUpdate:function(A){I(A,"forceUpdate")},enqueueReplaceState:function(A,M){I(A,"replaceState")},enqueueSetState:function(A,M){I(A,"setState")},enqueueSetProps:function(A,M){I(A,"setProps")},enqueueReplaceProps:function(A,M){I(A,"replaceProps")}});A.exports=g},function(A,M,t){"use strict";function I(A){function M(M,t,I,g,e,i){if(g=g||y,i=i||I,null==t[I]){var T=s[e];return M?new Error("Required "+T+" ` + "`" + `"+i+"` + "`" + ` was not specified in "+("` + "`" + `"+g+"` + "`" + `.")):null}return A(t,I,g,e,i)}var t=M.bind(null,!1);return t.isRequired=M.bind(null,!0),t}function g(A){function M(M,t,I,g,e){var i=M[t],T=a(i);if(T!==A){var E=s[g],N=B(i);return new Error("Invalid "+E+" ` + "`" + `"+e+"` + "`" + ` of type "+("` + "`" + `"+N+"` + "`" + ` supplied to ` + "`" + `"+I+"` + "`" + `, expected ")+("` + "`" + `"+A+"` + "`" + `."))}return null}return I(M)}function e(){return I(x.thatReturns(null))}function i(A){function M(M,t,I,g,e){var i=M[t];if(!Array.isArray(i)){var T=s[g],E=a(i);return new Error("Invalid "+T+" ` + "`" + `"+e+"` + "`" + ` of type "+("` + "`" + `"+E+"` + "`" + ` supplied to ` + "`" + `"+I+"` + "`" + `, expected an array."))}for(var N=0;N>"}var r=t(13),s=t(70),x=t(20),j=t(118),y="<>",u={array:g("array"),bool:g("boolean"),func:g("function"),number:g("number"),object:g("object"),string:g("string"),any:e(),arrayOf:i,element:T(),instanceOf:E,node:C(),objectOf:o,oneOf:N,oneOfType:n,shape:c};A.exports=u},function(A,M){"use strict";var t={injectCreateReactRootIndex:function(A){I.createReactRootIndex=A}},I={createReactRootIndex:null,injection:t};A.exports=I},function(A,M){"use strict";var t={currentScrollLeft:0,currentScrollTop:0,refreshScrollValues:function(A){t.currentScrollLeft=A.x,t.currentScrollTop=A.y}};A.exports=t},function(A,M,t){"use strict";function I(A,M){if(null==M?g(!1):void 0,null==A)return M;var t=Array.isArray(A),I=Array.isArray(M);return t&&I?(A.push.apply(A,M),A):t?(A.push(M),A):I?[A].concat(M):[A,M]}var g=t(2);A.exports=I},function(A,M){"use strict";var t=function(A,M,t){Array.isArray(A)?A.forEach(M,t):A&&M.call(t,A)};A.exports=t},function(A,M,t){"use strict";function I(){return!e&&g.canUseDOM&&(e="textContent"in document.documentElement?"textContent":"innerText"),e}var g=t(10),e=null;A.exports=I},function(A,M){"use strict";function t(A){var M=A&&A.nodeName&&A.nodeName.toLowerCase();return M&&("input"===M&&I[A.type]||"textarea"===M)}var I={color:!0,date:!0,datetime:!0,"datetime-local":!0,email:!0,month:!0,number:!0,password:!0,range:!0,search:!0,tel:!0,text:!0,time:!0,url:!0,week:!0};A.exports=t},function(A,M,t){"use strict";function I(A){return A&&A.__esModule?A:{default:A}}function g(){for(var A=arguments.length,M=Array(A),t=0;t=0&&s.splice(M,1)}function T(A){var M=document.createElement("style");return M.type="text/css",e(A,M),M}function E(A){var M=document.createElement("link");return M.rel="stylesheet",e(A,M),M}function N(A,M){var t,I,g;if(M.singleton){var e=r++;t=Q||(Q=T(M)),I=o.bind(null,t,e,!1),g=o.bind(null,t,e,!0)}else A.sourceMap&&"function"==typeof URL&&"function"==typeof URL.createObjectURL&&"function"==typeof URL.revokeObjectURL&&"function"==typeof Blob&&"function"==typeof btoa?(t=E(M),I=C.bind(null,t),g=function(){i(t),t.href&&URL.revokeObjectURL(t.href)}):(t=T(M),I=n.bind(null,t),g=function(){i(t)});return I(A),function(M){if(M){if(M.css===A.css&&M.media===A.media&&M.sourceMap===A.sourceMap)return;I(A=M)}else g()}}function o(A,M,t,I){var g=t?"":I.css;if(A.styleSheet)A.styleSheet.cssText=x(M,g);else{var e=document.createTextNode(g),i=A.childNodes;i[M]&&A.removeChild(i[M]),i.length?A.insertBefore(e,i[M]):A.appendChild(e)}}function n(A,M){var t=M.css,I=M.media;if(I&&A.setAttribute("media",I),A.styleSheet)A.styleSheet.cssText=t;else{for(;A.firstChild;)A.removeChild(A.firstChild);A.appendChild(document.createTextNode(t))}}function C(A,M){var t=M.css,I=M.sourceMap;I&&(t+="\n/*# sourceMappingURL=data:application/json;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(I))))+" */");var g=new Blob([t],{type:"text/css"}),e=A.href;A.href=URL.createObjectURL(g),e&&URL.revokeObjectURL(e)}var c={},D=function(A){var M;return function(){return"undefined"==typeof M&&(M=A.apply(this,arguments)),M}},a=D(function(){return/msie [6-9]\b/.test(window.navigator.userAgent.toLowerCase())}),B=D(function(){return document.head||document.getElementsByTagName("head")[0]}),Q=null,r=0,s=[];A.exports=function(A,M){M=M||{},"undefined"==typeof M.singleton&&(M.singleton=a()),"undefined"==typeof M.insertAt&&(M.insertAt="bottom");var t=g(A);return I(t,M),function(A){for(var e=[],i=0;i",'"',"` + "`" + `"," ","\r","\n","\t"],D=["{","}","|","\\","^","` + "`" + `"].concat(c),a=["'"].concat(D),B=["%","/","?",";","#"].concat(a),Q=["/","?","#"],r=255,s=/^[+a-z0-9A-Z_-]{0,63}$/,x=/^([+a-z0-9A-Z_-]{0,63})(.*)$/,j={javascript:!0,"javascript:":!0},y={javascript:!0,"javascript:":!0},u={http:!0,https:!0,ftp:!0,gopher:!0,file:!0,"http:":!0,"https:":!0,"ftp:":!0,"gopher:":!0,"file:":!0},w=t(360);I.prototype.parse=function(A,M,t){if(!N.isString(A))throw new TypeError("Parameter 'url' must be a string, not "+typeof A);var I=A.indexOf("?"),g=I!==-1&&I127?"x":O[F];if(!m.match(s)){var k=p.slice(0,Y),R=p.slice(Y+1),J=O.match(x);J&&(k.push(J[1]),R.unshift(J[2])),R.length&&(T="/"+R.join(".")+T),this.hostname=k.join(".");break}}}this.hostname.length>r?this.hostname="":this.hostname=this.hostname.toLowerCase(),z||(this.hostname=E.toASCII(this.hostname));var G=this.port?":"+this.port:"",H=this.hostname||"";this.host=H+G,this.href+=this.host,z&&(this.hostname=this.hostname.substr(1,this.hostname.length-2),"/"!==T[0]&&(T="/"+T))}if(!j[D])for(var Y=0,U=a.length;Y0)&&t.host.split("@");L&&(t.auth=L.shift(),t.host=t.hostname=L.shift())}return t.search=A.search,t.query=A.query,N.isNull(t.pathname)&&N.isNull(t.search)||(t.path=(t.pathname?t.pathname:"")+(t.search?t.search:"")),t.href=t.format(),t}if(!j.length)return t.pathname=null,t.search?t.path="/"+t.search:t.path=null,t.href=t.format(),t;for(var l=j.slice(-1)[0],Y=(t.host||A.host||j.length>1)&&("."===l||".."===l)||""===l,d=0,h=j.length;h>=0;h--)l=j[h],"."===l?j.splice(h,1):".."===l?(j.splice(h,1),d++):d&&(j.splice(h,1),d--);if(!s&&!x)for(;d--;d)j.unshift("..");!s||""===j[0]||j[0]&&"/"===j[0].charAt(0)||j.unshift(""),Y&&"/"!==j.join("/").substr(-1)&&j.push("");var S=""===j[0]||j[0]&&"/"===j[0].charAt(0);if(w){t.hostname=t.host=S?"":j.length?j.shift():"";var L=!!(t.host&&t.host.indexOf("@")>0)&&t.host.split("@");L&&(t.auth=L.shift(),t.host=t.hostname=L.shift())}return s=s||t.host&&j.length,s&&!S&&j.unshift(""),j.length?t.pathname=j.join("/"):(t.pathname=null,t.path=null),N.isNull(t.pathname)&&N.isNull(t.search)||(t.path=(t.pathname?t.pathname:"")+(t.search?t.search:"")),t.auth=A.auth||t.auth,t.slashes=t.slashes||A.slashes,t.href=t.format(),t},I.prototype.parseHost=function(){var A=this.host,M=n.exec(A);M&&(M=M[0],":"!==M&&(this.port=M.substr(1)),A=A.substr(0,A.length-M.length)),A&&(this.hostname=A)}},function(A,M){var t={animationIterationCount:!0,boxFlex:!0,boxFlexGroup:!0,boxOrdinalGroup:!0,columnCount:!0,flex:!0,flexGrow:!0,flexPositive:!0,flexShrink:!0,flexNegative:!0,flexOrder:!0,gridRow:!0,gridColumn:!0,fontWeight:!0,lineClamp:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,tabSize:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,stopOpacity:!0,strokeDashoffset:!0,strokeOpacity:!0,strokeWidth:!0};A.exports=function(A,M){return"number"!=typeof M||t[A]?M:M+"px"}},function(A,M,t){"use strict";function I(A){if(A&&A.__esModule)return A;var M={};if(null!=A)for(var t in A)Object.prototype.hasOwnProperty.call(A,t)&&(M[t]=A[t]);return M.default=A,M}function g(A){return A&&A.__esModule?A:{default:A}}function e(A,M,t){return P.LoggedIn()?t():(location.pathname!==z.minioBrowserPrefix&&location.pathname!==z.minioBrowserPrefix+"/"||M(z.minioBrowserPrefix+"/login"),t())}function i(A,M){P.LoggedIn()&&M(""+z.minioBrowserPrefix)}function T(){q<2&&setTimeout(function(){document.querySelector(".page-load").classList.add("pl-"+q),q++,T()},K[q])}t(499);var E=t(1),N=g(E),o=t(16),n=g(o),C=t(485),c=g(C),D=t(125),a=g(D),B=t(230),Q=g(B),r=t(190),s=g(r),x=t(191),j=g(x),y=t(65),u=g(y),w=t(187),L=g(w),l=t(394),Y=g(l),d=t(22),h=g(d),S=t(42),z=(g(S),t(26)),p=t(19),U=I(p),O=t(255),m=g(O),F=t(245),f=g(F),k=t(241),R=g(k),J=t(498),G=(g(J),t(131)),H=g(G),b=t(60),X=g(b);window.Web=H.default;var v=(0,Q.default)(c.default)(a.default)(m.default),W=(0,h.default)(function(A){return A})(R.default),V=(0,h.default)(function(A){return A})(f.default),P=new H.default(window.location.protocol+"//"+window.location.host+z.minioBrowserPrefix+"/webrpc",v.dispatch);window.web=P,v.dispatch(U.setWeb(P));var Z=function(A){return N.default.createElement("div",null,A.children)};n.default.render(N.default.createElement(Y.default,{store:v,web:P},N.default.createElement(j.default,{history:u.default},N.default.createElement(s.default,{path:"/",component:Z},N.default.createElement(s.default,{path:"minio",component:Z},N.default.createElement(L.default,{component:W,onEnter:e}),N.default.createElement(s.default,{path:"login",component:V,onEnter:i}),N.default.createElement(s.default,{path:":bucket",component:W,onEnter:e}),N.default.createElement(s.default,{path:":bucket/*",component:W,onEnter:e}))))),document.getElementById("root"));var K=[0,400],q=0;T(),X.default.getItem("newlyUpdated")&&(v.dispatch(U.showAlert({type:"success",message:"Updated to the latest UI Version."})),X.default.removeItem("newlyUpdated"))},function(A,M,t){"use strict";function I(A){if(A&&A.__esModule)return A;var M={};if(null!=A)for(var t in A)Object.prototype.hasOwnProperty.call(A,t)&&(M[t]=A[t]);return M.default=A,M}function g(A){return A&&A.__esModule?A:{default:A}}function e(A,M){if(!(A instanceof M))throw new TypeError("Cannot call a class as a function")}function i(A,M){if(!A)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!M||"object"!=typeof M&&"function"!=typeof M?A:M}function T(A,M){if("function"!=typeof M&&null!==M)throw new TypeError("Super expression must either be null or a function, not "+typeof M);A.prototype=Object.create(M&&M.prototype,{constructor:{value:A,enumerable:!1,writable:!0,configurable:!0}}),M&&(Object.setPrototypeOf?Object.setPrototypeOf(A,M):A.__proto__=M)}Object.defineProperty(M,"__esModule",{value:!0});var E=function(){function A(A,M){for(var t=0;t-1})))}},{key:"listObjects",value:function(){var A=this.props.dispatch;A(eA.listObjects())}},{key:"selectPrefix",value:function(A,M){A.preventDefault();var t=this.props,I=(t.dispatch,t.currentPath),g=(t.web,t.currentBucket),e=encodeURI(M);if(M.endsWith("/")||""===M){if(M===I)return;D.default.push(TA.pathJoin(g,e))}else window.location=window.location.origin+"/minio/download/"+g+"/"+e+"?token="+DA.default.getItem("token")}},{key:"makeBucket",value:function(A){A.preventDefault();var M=this.refs.makeBucketRef.value;this.refs.makeBucketRef.value="";var t=this.props,I=t.web,g=t.dispatch;this.hideMakeBucketModal(),I.MakeBucket({bucketName:M}).then(function(){g(eA.addBucket(M)),g(eA.selectBucket(M))}).catch(function(A){return g(eA.showAlert({type:"danger",message:A.message}))})}},{key:"hideMakeBucketModal",value:function(){var A=this.props.dispatch;A(eA.hideMakeBucketModal())}},{key:"showMakeBucketModal",value:function(A){A.preventDefault();var M=this.props.dispatch;M(eA.showMakeBucketModal())}},{key:"showAbout",value:function(A){A.preventDefault();var M=this.props.dispatch;M(eA.showAbout())}},{key:"hideAbout",value:function(A){A.preventDefault();var M=this.props.dispatch;M(eA.hideAbout())}},{key:"showBucketPolicy",value:function(A){A.preventDefault();var M=this.props.dispatch;M(eA.showBucketPolicy())}},{key:"hideBucketPolicy",value:function(A){A.preventDefault();var M=this.props.dispatch;M(eA.hideBucketPolicy())}},{key:"uploadFile",value:function(A){A.preventDefault();var M=this.props,t=M.dispatch,I=M.buckets;if(0===I.length)return void t(eA.showAlert({type:"danger",message:"Bucket needs to be created before trying to upload files."}));var g=A.target.files[0];A.target.value=null,this.xhr=new XMLHttpRequest,t(eA.uploadFile(g,this.xhr))}},{key:"removeObject",value:function(){var A=this,M=this.props,t=M.web,I=M.dispatch,g=(M.currentPath,M.currentBucket),e=M.deleteConfirmation;t.RemoveObject({bucketName:g,objectName:e.object}).then(function(){A.hideDeleteConfirmation(),I(eA.removeObject(e.object))}).catch(function(A){return I(eA.showAlert({type:"danger",message:A.message}))})}},{key:"hideAlert",value:function(A){A.preventDefault();var M=this.props.dispatch;M(eA.hideAlert())}},{key:"showDeleteConfirmation",value:function(A,M){A.preventDefault();var t=this.props.dispatch;t(eA.showDeleteConfirmation(M))}},{key:"hideDeleteConfirmation",value:function(){var A=this.props.dispatch;A(eA.hideDeleteConfirmation())}},{key:"shareObject",value:function(A,M){A.preventDefault();var t=this.props.dispatch;t(eA.shareObject(M))}},{key:"hideShareObjectModal",value:function(){var A=this.props.dispatch;A(eA.hideShareObject())}},{key:"dataType",value:function(A,M){return NA.getDataType(A,M)}},{key:"sortObjectsByName",value:function(A){var M=this.props,t=M.dispatch,I=M.objects,g=M.sortNameOrder;t(eA.setObjects(TA.sortObjectsByName(I,!g))),t(eA.setSortNameOrder(!g))}},{key:"sortObjectsBySize",value:function(){var A=this.props,M=A.dispatch,t=A.objects,I=A.sortSizeOrder;M(eA.setObjects(TA.sortObjectsBySize(t,!I))),M(eA.setSortSizeOrder(!I))}},{key:"sortObjectsByDate",value:function(){var A=this.props,M=A.dispatch,t=A.objects,I=A.sortDateOrder;M(eA.setObjects(TA.sortObjectsByDate(t,!I))),M(eA.setSortDateOrder(!I))}},{key:"logout",value:function(A){var M=this.props.web;A.preventDefault(),M.Logout(),D.default.push(oA.minioBrowserPrefix+"/login")}},{key:"fullScreen",value:function(A){A.preventDefault();var M=document.documentElement;M.requestFullscreen&&M.requestFullscreen(),M.mozRequestFullScreen&&M.mozRequestFullScreen(),M.webkitRequestFullscreen&&M.webkitRequestFullscreen(),M.msRequestFullscreen&&M.msRequestFullscreen()}},{key:"toggleSidebar",value:function(A){this.props.dispatch(eA.setSidebarStatus(A))}},{key:"hideSidebar",value:function(A){var M=A||window.event,t=M.srcElement||M.target;3===t.nodeType&&(t=t.parentNode);var I=t.id;"feh-trigger"!==I&&this.props.dispatch(eA.setSidebarStatus(!1))}},{key:"showSettings",value:function(A){A.preventDefault();var M=this.props.dispatch;M(eA.showSettings())}},{key:"showMessage",value:function(){var A=this.props.dispatch;A(eA.showAlert({type:"success",message:"Link copied to clipboard!"})),this.hideShareObjectModal()}},{key:"selectTexts",value:function(){this.refs.copyTextInput.select()}},{key:"handleExpireValue",value:function(A,M){M===-1?this.refs[A].stepDown(1):this.refs[A].stepUp(1),7==this.refs.expireDays.value&&(this.refs.expireHours.value=0,this.refs.expireMins.value=0)}},{key:"render",value:function(){var A=this.props.storageInfo,M=A.total,t=A.free,I=this.props,g=I.showMakeBucketModal,e=I.alert,i=I.sortNameOrder,T=I.sortSizeOrder,E=I.sortDateOrder,N=I.showAbout,n=I.showBucketPolicy,c=this.props.serverInfo,D=c.version,a=c.memory,Q=c.platform,r=c.runtime,x=this.props.sidebarStatus,y=this.props.showSettings,w=this.props,l=w.policies,d=w.currentBucket,S=(w.currentPath,this.props.deleteConfirmation),p=this.props.shareObject,U=this.props,O=U.web,F=U.prefixWritable,k=U.istruncated,J=y?o.default.createElement(V.default,null):o.default.createElement("noscript",null),H=o.default.createElement(L.default,{className:(0,C.default)({alert:!0,animated:!0,fadeInDown:e.show,fadeOutUp:!e.show}),bsStyle:e.type,onDismiss:this.hideAlert.bind(this)},o.default.createElement("div",{className:"text-center"},e.message));e.message||(H="");var X=(o.default.createElement(h.default,{id:"tt-sign-out"},"Sign out"),o.default.createElement(h.default,{id:"tt-upload-file"},"Upload file")),W=o.default.createElement(h.default,{id:"tt-create-bucket"},"Create bucket"),P="",K="",_="",AA=M-t,tA=AA/M*100+"%";O.LoggedIn()?K=o.default.createElement($.default,{fullScreenFunc:this.fullScreen.bind(this),aboutFunc:this.showAbout.bind(this),settingsFunc:this.showSettings.bind(this),logoutFunc:this.logout.bind(this)}):P=o.default.createElement("a",{className:"btn btn-danger",href:"/minio/login"},"Login"),O.LoggedIn()&&(_=o.default.createElement("div",{className:"feh-usage"},o.default.createElement("div",{className:"fehu-chart"},o.default.createElement("div",{style:{width:tA}})),o.default.createElement("ul",null,o.default.createElement("li",null,"Used:",B.default.filesize(M-t)),o.default.createElement("li",{className:"pull-right"},"Free:",B.default.filesize(M-AA)))));var gA="";return O.LoggedIn()?gA=o.default.createElement(z.default,{dropup:!0,className:"feb-actions",id:"fe-action-toggle"},o.default.createElement(z.default.Toggle,{noCaret:!0,className:"feba-toggle"},o.default.createElement("span",null,o.default.createElement("i",{className:"fa fa-plus"}))),o.default.createElement(z.default.Menu,null,o.default.createElement(Y.default,{placement:"left",overlay:X},o.default.createElement("a",{href:"#",className:"feba-btn feba-upload"},o.default.createElement("input",{type:"file",onChange:this.uploadFile.bind(this),style:{display:"none"},id:"file-input"}),o.default.createElement("label",{htmlFor:"file-input"}," ",o.default.createElement("i",{className:"fa fa-cloud-upload"})," "))),o.default.createElement(Y.default,{placement:"left",overlay:W},o.default.createElement("a",{href:"#",className:"feba-btn feba-bucket",onClick:this.showMakeBucketModal.bind(this)},o.default.createElement("i",{className:"fa fa-hdd-o"}))))):F&&(gA=o.default.createElement(z.default,{dropup:!0,className:"feb-actions",id:"fe-action-toggle"},o.default.createElement(z.default.Toggle,{noCaret:!0,className:"feba-toggle"},o.default.createElement("span",null,o.default.createElement("i",{className:"fa fa-plus"}))),o.default.createElement(z.default.Menu,null,o.default.createElement(Y.default,{placement:"left",overlay:X},o.default.createElement("a",{href:"#",className:"feba-btn feba-upload"},o.default.createElement("input",{type:"file",onChange:this.uploadFile.bind(this),style:{display:"none"},id:"file-input"}),o.default.createElement("label",{htmlFor:"file-input"}," ",o.default.createElement("i",{className:"fa fa-cloud-upload"})," ")))))),o.default.createElement("div",{className:(0,C.default)({"file-explorer":!0,toggled:x})},o.default.createElement(R.default,{searchBuckets:this.searchBuckets.bind(this),selectBucket:this.selectBucket.bind(this),clickOutside:this.hideSidebar.bind(this),showPolicy:this.showBucketPolicy.bind(this)}),o.default.createElement("div",{className:"fe-body"},o.default.createElement(m.default,null,H,o.default.createElement("header",{className:"fe-header-mobile hidden-lg hidden-md"},o.default.createElement("div",{id:"feh-trigger",className:"feh-trigger "+(0,C.default)({"feht-toggled":x}),onClick:this.toggleSidebar.bind(this,!x)},o.default.createElement("div",{className:"feht-lines"},o.default.createElement("div",{className:"top"}),o.default.createElement("div",{className:"center"}),o.default.createElement("div",{className:"bottom"}))),o.default.createElement("img",{className:"mh-logo",src:IA.default,alt:""})),o.default.createElement("header",{className:"fe-header"},o.default.createElement(G.default,{selectPrefix:this.selectPrefix.bind(this)}),_,o.default.createElement("ul",{className:"feh-actions"},o.default.createElement(b.default,null),P,K)),o.default.createElement("div",{className:"feb-container"},o.default.createElement("header",{className:"fesl-row","data-type":"folder"},o.default.createElement("div",{className:"fesl-item fi-name",onClick:this.sortObjectsByName.bind(this),"data-sort":"name"},"Name",o.default.createElement("i",{className:(0,C.default)({"fesli-sort":!0,fa:!0,"fa-sort-alpha-desc":i,"fa-sort-alpha-asc":!i})})),o.default.createElement("div",{className:"fesl-item fi-size",onClick:this.sortObjectsBySize.bind(this),"data-sort":"size"},"Size",o.default.createElement("i",{className:(0,C.default)({"fesli-sort":!0,fa:!0,"fa-sort-amount-desc":T,"fa-sort-amount-asc":!T})})),o.default.createElement("div",{className:"fesl-item fi-modified",onClick:this.sortObjectsByDate.bind(this),"data-sort":"last-modified"},"Last Modified",o.default.createElement("i",{className:(0,C.default)({"fesli-sort":!0,fa:!0,"fa-sort-numeric-desc":E,"fa-sort-numeric-asc":!E})})),o.default.createElement("div",{className:"fesl-item fi-actions"}))),o.default.createElement("div",{className:"feb-container"},o.default.createElement(BA.default,{loadMore:this.listObjects.bind(this),hasMore:k,useWindow:!0,initialLoad:!1},o.default.createElement(f.default,{dataType:this.dataType.bind(this),selectPrefix:this.selectPrefix.bind(this),showDeleteConfirmation:this.showDeleteConfirmation.bind(this),shareObject:this.shareObject.bind(this)})),o.default.createElement("div",{className:"text-center",style:{display:k?"block":"none"}},o.default.createElement("span",null,"Loading..."))),o.default.createElement(v.default,null),gA,o.default.createElement(s.default,{className:"modal-create-bucket",bsSize:"small",animation:!1,show:g,onHide:this.hideMakeBucketModal.bind(this)},o.default.createElement("button",{className:"close close-alt",onClick:this.hideMakeBucketModal.bind(this)},o.default.createElement("span",null,"×")),o.default.createElement(j.default,null,o.default.createElement("form",{onSubmit:this.makeBucket.bind(this)},o.default.createElement("div",{className:"input-group"},o.default.createElement("input",{className:"ig-text",type:"text",ref:"makeBucketRef",placeholder:"Bucket Name",autoFocus:!0}),o.default.createElement("i",{className:"ig-helpers"}))))),o.default.createElement(s.default,{className:"modal-about modal-dark",animation:!1,show:N,onHide:this.hideAbout.bind(this)},o.default.createElement("button",{className:"close",onClick:this.hideAbout.bind(this)},o.default.createElement("span",null,"×")),o.default.createElement("div",{className:"ma-inner"},o.default.createElement("div",{className:"mai-item hidden-xs"},o.default.createElement("a",{href:"https://minio.io",target:"_blank"},o.default.createElement("img",{className:"maii-logo",src:IA.default,alt:""}))),o.default.createElement("div",{className:"mai-item"},o.default.createElement("ul",{className:"maii-list"},o.default.createElement("li",null,o.default.createElement("div",null,"Version"),o.default.createElement("small",null,D)),o.default.createElement("li",null,o.default.createElement("div",null,"Memory"),o.default.createElement("small",null,a)),o.default.createElement("li",null,o.default.createElement("div",null,"Platform"),o.default.createElement("small",null,Q)),o.default.createElement("li",null,o.default.createElement("div",null,"Runtime"),o.default.createElement("small",null,r)))))),o.default.createElement(s.default,{className:"modal-policy",animation:!1,show:n,onHide:this.hideBucketPolicy.bind(this)},o.default.createElement(u.default,null,"Bucket Policy (",d,")",o.default.createElement("button",{className:"close close-alt",onClick:this.hideBucketPolicy.bind(this)},o.default.createElement("span",null,"×"))),o.default.createElement("div",{className:"pm-body"},o.default.createElement(Z.default,{bucket:d}),l.map(function(A,M){return o.default.createElement(q.default,{key:M,prefix:A.prefix,policy:A.policy})}))),o.default.createElement(MA.default,{show:S.show,icon:"fa fa-exclamation-triangle mci-red",text:"Are you sure you want to delete?",sub:"This cannot be undone!",okText:"Delete",cancelText:"Cancel",okHandler:this.removeObject.bind(this),cancelHandler:this.hideDeleteConfirmation.bind(this)}),o.default.createElement(s.default,{show:p.show,animation:!1,onHide:this.hideShareObjectModal.bind(this),bsSize:"small"},o.default.createElement(u.default,null,"Share Object"),o.default.createElement(j.default,null,o.default.createElement("div",{className:"input-group copy-text"},o.default.createElement("label",null,"Shareable Link"),o.default.createElement("input",{type:"text",ref:"copyTextInput",readOnly:"readOnly",value:window.location.protocol+"//"+p.url,onClick:this.selectTexts.bind(this)})),o.default.createElement("div",{className:"input-group",style:{display:O.LoggedIn()?"block":"none"}},o.default.createElement("label",null,"Expires in"),o.default.createElement("div",{className:"set-expire"},o.default.createElement("div",{className:"set-expire-item"},o.default.createElement("i",{className:"set-expire-increase",onClick:this.handleExpireValue.bind(this,"expireDays",1)}),o.default.createElement("div",{className:"set-expire-title"},"Days"),o.default.createElement("div",{className:"set-expire-value"},o.default.createElement("input",{ref:"expireDays",type:"number",min:0,max:7,defaultValue:0})),o.default.createElement("i",{className:"set-expire-decrease",onClick:this.handleExpireValue.bind(this,"expireDays",-1)})),o.default.createElement("div",{className:"set-expire-item"},o.default.createElement("i",{className:"set-expire-increase",onClick:this.handleExpireValue.bind(this,"expireHours",1)}),o.default.createElement("div",{className:"set-expire-title"},"Hours"),o.default.createElement("div",{className:"set-expire-value"},o.default.createElement("input",{ref:"expireHours",type:"number",min:0,max:24,defaultValue:0})),o.default.createElement("i",{className:"set-expire-decrease",onClick:this.handleExpireValue.bind(this,"expireHours",-1)})),o.default.createElement("div",{className:"set-expire-item"},o.default.createElement("i",{className:"set-expire-increase",onClick:this.handleExpireValue.bind(this,"expireMins",1)}),o.default.createElement("div",{className:"set-expire-title"},"Minutes"),o.default.createElement("div",{className:"set-expire-value"},o.default.createElement("input",{ref:"expireMins",type:"number",min:1,max:60,defaultValue:45})),o.default.createElement("i",{className:"set-expire-decrease",onClick:this.handleExpireValue.bind(this,"expireMins",-1)}))))),o.default.createElement("div",{className:"modal-footer"},o.default.createElement(CA.default,{text:p.url,onCopy:this.showMessage.bind(this)},o.default.createElement("button",{className:"btn btn-success"},"Copy Link")),o.default.createElement("button",{className:"btn btn-link",onClick:this.hideShareObjectModal.bind(this)},"Cancel"))),J)))}}]),M}(o.default.Component);M.default=QA},function(A,M,t){"use strict";function I(A){return A&&A.__esModule?A:{default:A}}Object.defineProperty(M,"__esModule",{value:!0});var g=t(1),e=I(g),i=t(22),T=I(i),E=t(91),N=I(E),o=function(A){var M=A.fullScreenFunc,t=A.aboutFunc,I=A.settingsFunc,g=A.logoutFunc;return e.default.createElement("li",null,e.default.createElement(N.default,{pullRight:!0,id:"top-right-menu"},e.default.createElement(N.default.Toggle,{noCaret:!0},e.default.createElement("i",{className:"fa fa-reorder"})),e.default.createElement(N.default.Menu,{className:"dropdown-menu-right"},e.default.createElement("li",null,e.default.createElement("a",{target:"_blank",href:"https://github.com/minio/miniobrowser"},"Github ",e.default.createElement("i",{className:"fa fa-github"}))),e.default.createElement("li",null,e.default.createElement("a",{href:"",onClick:M},"Fullscreen ",e.default.createElement("i",{className:"fa fa-expand"}))),e.default.createElement("li",null,e.default.createElement("a",{target:"_blank",href:"https://docs.minio.io/"},"Documentation ",e.default.createElement("i",{className:"fa fa-book"}))),e.default.createElement("li",null,e.default.createElement("a",{target:"_blank",href:"https://slack.minio.io"},"Ask for help ",e.default.createElement("i",{className:"fa fa-question-circle"}))),e.default.createElement("li",null,e.default.createElement("a",{href:"",onClick:t},"About ",e.default.createElement("i",{className:"fa fa-info-circle"}))),e.default.createElement("li",null,e.default.createElement("a",{href:"",onClick:I},"Settings ",e.default.createElement("i",{className:"fa fa-cog"}))),e.default.createElement("li",null,e.default.createElement("a",{href:"",onClick:g},"Sign Out ",e.default.createElement("i",{className:"fa fa-sign-out"}))))))};M.default=(0,T.default)(function(A){return A})(o)},function(A,M,t){"use strict";function I(A){return A&&A.__esModule?A:{default:A}}Object.defineProperty(M,"__esModule",{value:!0});var g=t(1),e=I(g),i=t(22),T=I(i),E=t(95),N=I(E),o=t(94),n=I(o),C=function(A){var M=A.latestUiVersion;return M===currentUiVersion?e.default.createElement("noscript",null):e.default.createElement("li",{className:"hidden-xs hidden-sm"},e.default.createElement("a",{href:""},e.default.createElement(n.default,{placement:"left",overlay:e.default.createElement(N.default,{id:"tt-version-update"},"New update available. Click to refresh.")}," ",e.default.createElement("i",{className:"fa fa-refresh"})," ")))};M.default=(0,T.default)(function(A){return{latestUiVersion:A.latestUiVersion}})(C)},function(A,M,t){"use strict";function I(A){if(A&&A.__esModule)return A;var M={};if(null!=A)for(var t in A)Object.prototype.hasOwnProperty.call(A,t)&&(M[t]=A[t]);return M.default=A,M}function g(A){ -return A&&A.__esModule?A:{default:A}}function e(A,M){if(!(A instanceof M))throw new TypeError("Cannot call a class as a function")}function i(A,M){if(!A)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!M||"object"!=typeof M&&"function"!=typeof M?A:M}function T(A,M){if("function"!=typeof M&&null!==M)throw new TypeError("Super expression must either be null or a function, not "+typeof M);A.prototype=Object.create(M&&M.prototype,{constructor:{value:A,enumerable:!1,writable:!0,configurable:!0}}),M&&(Object.setPrototypeOf?Object.setPrototypeOf(A,M):A.__proto__=M)}Object.defineProperty(M,"__esModule",{value:!0});var E=function(){function A(A,M){for(var t=0;t0&&void 0!==arguments[0]?arguments[0]:{buckets:[],visibleBuckets:[],objects:[],istruncated:!0,storageInfo:{},serverInfo:{},currentBucket:"",currentPath:"",showMakeBucketModal:!1,uploads:{},alert:{show:!1,type:"danger",message:""},loginError:!1,sortNameOrder:!1,sortSizeOrder:!1,sortDateOrder:!1,latestUiVersion:currentUiVersion,sideBarActive:!1,loginRedirectPath:E.minioBrowserPrefix,settings:{accessKey:"",secretKey:"",secretKeyVisible:!1},showSettings:!1,policies:[],deleteConfirmation:{object:"",show:!1},shareObject:{show:!1,url:"",expiry:604800},prefixWritable:!1},M=arguments[1],t=Object.assign({},A);switch(M.type){case T.SET_WEB:t.web=M.web;break;case T.SET_BUCKETS:t.buckets=M.buckets;break;case T.ADD_BUCKET:t.buckets=[M.bucket].concat(e(t.buckets)),t.visibleBuckets=[M.bucket].concat(e(t.visibleBuckets));break;case T.SET_VISIBLE_BUCKETS:t.visibleBuckets=M.visibleBuckets;break;case T.SET_CURRENT_BUCKET:t.currentBucket=M.currentBucket;break;case T.SET_OBJECTS:M.objects.length?(t.objects=[].concat(e(t.objects),e(M.objects)),t.marker=M.marker,t.istruncated=M.istruncated):(t.objects=[],t.marker="",t.istruncated=M.istruncated);break;case T.SET_CURRENT_PATH:t.currentPath=M.currentPath;break;case T.SET_STORAGE_INFO:t.storageInfo=M.storageInfo;break;case T.SET_SERVER_INFO:t.serverInfo=M.serverInfo;break;case T.SHOW_MAKEBUCKET_MODAL:t.showMakeBucketModal=M.showMakeBucketModal;break;case T.UPLOAD_PROGRESS:t.uploads=Object.assign({},t.uploads),t.uploads[M.slug].loaded=M.loaded;break;case T.ADD_UPLOAD:t.uploads=Object.assign({},t.uploads,g({},M.slug,{loaded:0,size:M.size,xhr:M.xhr,name:M.name}));break;case T.STOP_UPLOAD:t.uploads=Object.assign({},t.uploads),delete t.uploads[M.slug];break;case T.SET_ALERT:t.alert.alertTimeout&&clearTimeout(t.alert.alertTimeout),M.alert.show?t.alert=M.alert:t.alert=Object.assign({},t.alert,{show:!1});break;case T.SET_LOGIN_ERROR:t.loginError=!0;break;case T.SET_SHOW_ABORT_MODAL:t.showAbortModal=M.showAbortModal;break;case T.SHOW_ABOUT:t.showAbout=M.showAbout;break;case T.SET_SORT_NAME_ORDER:t.sortNameOrder=M.sortNameOrder;break;case T.SET_SORT_SIZE_ORDER:t.sortSizeOrder=M.sortSizeOrder;break;case T.SET_SORT_DATE_ORDER:t.sortDateOrder=M.sortDateOrder;break;case T.SET_LATEST_UI_VERSION:t.latestUiVersion=M.latestUiVersion;break;case T.SET_SIDEBAR_STATUS:t.sidebarStatus=M.sidebarStatus;break;case T.SET_LOGIN_REDIRECT_PATH:t.loginRedirectPath=M.path;case T.SET_LOAD_BUCKET:t.loadBucket=M.loadBucket;break;case T.SET_LOAD_PATH:t.loadPath=M.loadPath;break;case T.SHOW_SETTINGS:t.showSettings=M.showSettings;break;case T.SET_SETTINGS:t.settings=Object.assign({},t.settings,M.settings);break;case T.SHOW_BUCKET_POLICY:t.showBucketPolicy=M.showBucketPolicy;break;case T.SET_POLICIES:t.policies=M.policies;break;case T.DELETE_CONFIRMATION:t.deleteConfirmation=Object.assign({},M.payload);break;case T.SET_SHARE_OBJECT:t.shareObject=Object.assign({},M.shareObject);break;case T.SET_PREFIX_WRITABLE:t.prefixWritable=M.prefixWritable;break;case T.REMOVE_OBJECT:var I=t.objects.findIndex(function(A){return A.name===M.object});if(I==-1)break;t.objects=[].concat(e(t.objects.slice(0,I)),e(t.objects.slice(I+1)))}return t}},function(A,M,t){A.exports={default:t(260),__esModule:!0}},function(A,M,t){t(270),A.exports=t(49).Object.assign},function(A,M,t){var I=t(80);A.exports=function(A,M){return I.create(A,M)}},function(A,M,t){t(271),A.exports=t(49).Object.keys},function(A,M,t){t(272),A.exports=t(49).Object.setPrototypeOf},function(A,M){A.exports=function(A){if("function"!=typeof A)throw TypeError(A+" is not a function!");return A}},function(A,M,t){var I=t(136);A.exports=function(A){if(!I(A))throw TypeError(A+" is not an object!");return A}},function(A,M){var t={}.toString;A.exports=function(A){return t.call(A).slice(8,-1)}},function(A,M){A.exports=function(A){if(void 0==A)throw TypeError("Can't call method on "+A);return A}},function(A,M){var t=A.exports="undefined"!=typeof window&&window.Math==Math?window:"undefined"!=typeof self&&self.Math==Math?self:Function("return this")();"number"==typeof __g&&(__g=t)},function(A,M,t){var I=t(263);A.exports=Object("z").propertyIsEnumerable(0)?Object:function(A){return"String"==I(A)?A.split(""):Object(A)}},function(A,M,t){var I=t(80),g=t(137),e=t(266);A.exports=t(135)(function(){var A=Object.assign,M={},t={},I=Symbol(),g="abcdefghijklmnopqrst";return M[I]=7,g.split("").forEach(function(A){t[A]=A}),7!=A({},M)[I]||Object.keys(A({},t)).join("")!=g})?function(A,M){for(var t=g(A),i=arguments,T=i.length,E=1,N=I.getKeys,o=I.getSymbols,n=I.isEnum;T>E;)for(var C,c=e(i[E++]),D=o?N(c).concat(o(c)):N(c),a=D.length,B=0;a>B;)n.call(c,C=D[B++])&&(t[C]=c[C]);return t}:Object.assign},function(A,M,t){var I=t(79),g=t(49),e=t(135);A.exports=function(A,M){var t=(g.Object||{})[A]||Object[A],i={};i[A]=M(t),I(I.S+I.F*e(function(){t(1)}),"Object",i)}},function(A,M,t){var I=t(80).getDesc,g=t(136),e=t(262),i=function(A,M){if(e(A),!g(M)&&null!==M)throw TypeError(M+": can't set as prototype!")};A.exports={set:Object.setPrototypeOf||("__proto__"in{}?function(A,M,g){try{g=t(134)(Function.call,I(Object.prototype,"__proto__").set,2), -g(A,[]),M=!(A instanceof Array)}catch(A){M=!0}return function(A,t){return i(A,t),M?A.__proto__=t:g(A,t),A}}({},!1):void 0),check:i}},function(A,M,t){var I=t(79);I(I.S+I.F,"Object",{assign:t(267)})},function(A,M,t){var I=t(137);t(268)("keys",function(A){return function(M){return A(I(M))}})},function(A,M,t){var I=t(79);I(I.S,"Object",{setPrototypeOf:t(269).set})},function(A,M,t){function I(A){if(A)return g(A)}function g(A){for(var M in I.prototype)A[M]=I.prototype[M];return A}A.exports=I,I.prototype.on=I.prototype.addEventListener=function(A,M){return this._callbacks=this._callbacks||{},(this._callbacks["$"+A]=this._callbacks["$"+A]||[]).push(M),this},I.prototype.once=function(A,M){function t(){this.off(A,t),M.apply(this,arguments)}return t.fn=M,this.on(A,t),this},I.prototype.off=I.prototype.removeListener=I.prototype.removeAllListeners=I.prototype.removeEventListener=function(A,M){if(this._callbacks=this._callbacks||{},0==arguments.length)return this._callbacks={},this;var t=this._callbacks["$"+A];if(!t)return this;if(1==arguments.length)return delete this._callbacks["$"+A],this;for(var I,g=0;g-1&&A%1==0&&A1)for(var t=1;tA.clientHeight}Object.defineProperty(M,"__esModule",{value:!0}),M.default=i;var T=t(57),E=I(T),N=t(39),o=I(N);A.exports=M.default},function(A,M,t){"use strict";function I(A){return A&&A.__esModule?A:{default:A}}function g(A,M,t,I,g){var i=A[M],E="undefined"==typeof i?"undefined":e(i);return T.default.isValidElement(i)?new Error("Invalid "+I+" ` + "`" + `"+g+"` + "`" + ` of type ReactElement "+("supplied to ` + "`" + `"+t+"` + "`" + `, expected an element type (a string ")+"or a ReactClass)."):"function"!==E&&"string"!==E?new Error("Invalid "+I+" ` + "`" + `"+g+"` + "`" + ` of value ` + "`" + `"+i+"` + "`" + ` "+("supplied to ` + "`" + `"+t+"` + "`" + `, expected an element type (a string ")+"or a ReactClass)."):null}M.__esModule=!0;var e="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(A){return typeof A}:function(A){return A&&"function"==typeof Symbol&&A.constructor===Symbol?"symbol":typeof A},i=t(1),T=I(i),E=t(180),N=I(E);M.default=(0,N.default)(g)},function(A,M){"use strict";function t(A){function M(M,t,I,g,e,i){var T=g||"<>",E=i||I;if(null==t[I])return M?new Error("Required "+e+" ` + "`" + `"+E+"` + "`" + ` was not specified "+("in ` + "`" + `"+T+"` + "`" + `.")):null;for(var N=arguments.length,o=Array(N>6?N-6:0),n=6;n=0||Object.prototype.hasOwnProperty.call(A,I)&&(t[I]=A[I]);return t}function e(A){return 0===A.button}function i(A){return!!(A.metaKey||A.altKey||A.ctrlKey||A.shiftKey)}function T(A){for(var M in A)if(Object.prototype.hasOwnProperty.call(A,M))return!1;return!0}function E(A,M){var t=M.query,I=M.hash,g=M.state;return t||I||g?{pathname:A,query:t,hash:I,state:g}:A}M.__esModule=!0;var N=Object.assign||function(A){for(var M=1;M=0;I--){var g=A[I],e=g.path||"";if(t=e.replace(/\/*$/,"/")+t,0===e.indexOf("/"))break}return"/"+t}},propTypes:{path:C,from:C,to:C.isRequired,query:c,state:c,onEnter:o.falsy,children:o.falsy},render:function(){(0,T.default)(!1)}});M.default=D,A.exports=M.default},function(A,M,t){"use strict";function I(A){return A&&A.__esModule?A:{default:A}}M.__esModule=!0;var g=t(1),e=I(g),i=t(8),T=I(i),E=t(30),N=t(34),o=e.default.PropTypes,n=o.string,C=o.func,c=e.default.createClass({displayName:"Route",statics:{createRouteFromReactElement:E.createRouteFromReactElement},propTypes:{path:n,component:N.component,components:N.components,getComponent:C,getComponents:C},render:function(){(0,T.default)(!1)}});M.default=c,A.exports=M.default},function(A,M,t){"use strict";function I(A){return A&&A.__esModule?A:{default:A}}function g(A,M){var t={};for(var I in A)M.indexOf(I)>=0||Object.prototype.hasOwnProperty.call(A,I)&&(t[I]=A[I]);return t}function e(A){return!A||!A.__v2_compatible__}function i(A){return A&&A.getCurrentLocation}M.__esModule=!0;var T=Object.assign||function(A){for(var M=1;M=0&&0===window.sessionStorage.length)return;throw A}}function i(A){var M=void 0;try{M=window.sessionStorage.getItem(g(A))}catch(A){if(A.name===o)return null}if(M)try{return JSON.parse(M)}catch(A){}return null}M.__esModule=!0,M.saveState=e,M.readState=i;var T=t(23),E=(I(T),"@@History/"),N=["QuotaExceededError","QUOTA_EXCEEDED_ERR"],o="SecurityError"},function(A,M,t){"use strict";function I(A){return A&&A.__esModule?A:{default:A}}function g(A){function M(A){return E.canUseDOM?void 0:T.default(!1),t.listen(A)}var t=n.default(e({getUserConfirmation:N.getUserConfirmation},A,{go:N.go}));return e({},t,{listen:M})}M.__esModule=!0;var e=Object.assign||function(A){for(var M=1;M1?M-1:0),e=1;e=A.childNodes.length?null:A.childNodes.item(t);A.insertBefore(M,I)}var g=t(433),e=t(218),i=t(17),T=t(76),E=t(121),N=t(2),o={dangerouslyReplaceNodeWithMarkup:g.dangerouslyReplaceNodeWithMarkup,updateTextContent:E,processUpdates:function(A,M){for(var t,i=null,o=null,n=0;n-1?void 0:i(!1),!N.plugins[t]){M.extractEvents?void 0:i(!1),N.plugins[t]=M;var I=M.eventTypes;for(var e in I)g(I[e],M,e)?void 0:i(!1)}}}function g(A,M,t){N.eventNameDispatchConfigs.hasOwnProperty(t)?i(!1):void 0,N.eventNameDispatchConfigs[t]=A;var I=A.phasedRegistrationNames;if(I){for(var g in I)if(I.hasOwnProperty(g)){var T=I[g];e(T,M,t)}return!0}return!!A.registrationName&&(e(A.registrationName,M,t),!0)}function e(A,M,t){N.registrationNameModules[A]?i(!1):void 0,N.registrationNameModules[A]=M,N.registrationNameDependencies[A]=M.eventTypes[t].dependencies}var i=t(2),T=null,E={},N={plugins:[],eventNameDispatchConfigs:{},registrationNameModules:{},registrationNameDependencies:{},injectEventPluginOrder:function(A){T?i(!1):void 0,T=Array.prototype.slice.call(A),I()},injectEventPluginsByName:function(A){var M=!1;for(var t in A)if(A.hasOwnProperty(t)){var g=A[t];E.hasOwnProperty(t)&&E[t]===g||(E[t]?i(!1):void 0,E[t]=g,M=!0)}M&&I()},getPluginModuleForEvent:function(A){var M=A.dispatchConfig;if(M.registrationName)return N.registrationNameModules[M.registrationName]||null;for(var t in M.phasedRegistrationNames)if(M.phasedRegistrationNames.hasOwnProperty(t)){var I=N.registrationNameModules[M.phasedRegistrationNames[t]];if(I)return I}return null},_resetEventPlugins:function(){T=null;for(var A in E)E.hasOwnProperty(A)&&delete E[A];N.plugins.length=0;var M=N.eventNameDispatchConfigs;for(var t in M)M.hasOwnProperty(t)&&delete M[t];var I=N.registrationNameModules;for(var g in I)I.hasOwnProperty(g)&&delete I[g]}};A.exports=N},function(A,M,t){"use strict";function I(A){return(""+A).replace(x,"//")}function g(A,M){this.func=A,this.context=M,this.count=0}function e(A,M,t){var I=A.func,g=A.context;I.call(g,M,A.count++)}function i(A,M,t){if(null==A)return A;var I=g.getPooled(M,t);Q(A,e,I),g.release(I)}function T(A,M,t,I){this.result=A,this.keyPrefix=M,this.func=t,this.context=I,this.count=0}function E(A,M,t){var g=A.result,e=A.keyPrefix,i=A.func,T=A.context,E=i.call(T,M,A.count++);Array.isArray(E)?N(E,g,t,B.thatReturnsArgument):null!=E&&(a.isValidElement(E)&&(E=a.cloneAndReplaceKey(E,e+(E!==M?I(E.key||"")+"/":"")+t)),g.push(E))}function N(A,M,t,g,e){var i="";null!=t&&(i=I(t)+"/");var N=T.getPooled(M,i,g,e);Q(A,E,N),T.release(N)}function o(A,M,t){if(null==A)return A;var I=[];return N(A,I,null,M,t),I}function n(A,M,t){return null}function C(A,M){return Q(A,n,null)}function c(A){var M=[];return N(A,M,null,B.thatReturnsArgument),M}var D=t(31),a=t(13),B=t(20),Q=t(123),r=D.twoArgumentPooler,s=D.fourArgumentPooler,x=/\/(?!\/)/g;g.prototype.destructor=function(){this.func=null,this.context=null,this.count=0},D.addPoolingTo(g,r),T.prototype.destructor=function(){this.result=null,this.keyPrefix=null,this.func=null,this.context=null,this.count=0},D.addPoolingTo(T,s);var j={forEach:i,map:o,mapIntoWithKeyPrefixInternal:N,count:C,toArray:c};A.exports=j},function(A,M,t){"use strict";function I(A,M){var t=y.hasOwnProperty(M)?y[M]:null;w.hasOwnProperty(M)&&(t!==x.OVERRIDE_BASE?B(!1):void 0),A.hasOwnProperty(M)&&(t!==x.DEFINE_MANY&&t!==x.DEFINE_MANY_MERGED?B(!1):void 0)}function g(A,M){if(M){"function"==typeof M?B(!1):void 0,C.isValidElement(M)?B(!1):void 0;var t=A.prototype;M.hasOwnProperty(s)&&u.mixins(A,M.mixins);for(var g in M)if(M.hasOwnProperty(g)&&g!==s){var e=M[g];if(I(t,g),u.hasOwnProperty(g))u[g](A,e);else{var i=y.hasOwnProperty(g),N=t.hasOwnProperty(g),o="function"==typeof e,n=o&&!i&&!N&&M.autobind!==!1;if(n)t.__reactAutoBindMap||(t.__reactAutoBindMap={}),t.__reactAutoBindMap[g]=e,t[g]=e;else if(N){var c=y[g];!i||c!==x.DEFINE_MANY_MERGED&&c!==x.DEFINE_MANY?B(!1):void 0,c===x.DEFINE_MANY_MERGED?t[g]=T(t[g],e):c===x.DEFINE_MANY&&(t[g]=E(t[g],e))}else t[g]=e}}}}function e(A,M){if(M)for(var t in M){var I=M[t];if(M.hasOwnProperty(t)){var g=t in u;g?B(!1):void 0;var e=t in A;e?B(!1):void 0,A[t]=I}}}function i(A,M){A&&M&&"object"==typeof A&&"object"==typeof M?void 0:B(!1);for(var t in M)M.hasOwnProperty(t)&&(void 0!==A[t]?B(!1):void 0,A[t]=M[t]);return A}function T(A,M){return function(){var t=A.apply(this,arguments),I=M.apply(this,arguments);if(null==t)return I;if(null==I)return t;var g={};return i(g,t),i(g,I),g}}function E(A,M){return function(){A.apply(this,arguments),M.apply(this,arguments)}}function N(A,M){var t=M.bind(A);return t}function o(A){for(var M in A.__reactAutoBindMap)if(A.__reactAutoBindMap.hasOwnProperty(M)){var t=A.__reactAutoBindMap[M];A[M]=N(A,t)}}var n=t(205),C=t(13),c=(t(71),t(70),t(220)),D=t(4),a=t(50),B=t(2),Q=t(59),r=t(27),s=(t(3),r({mixins:null})),x=Q({DEFINE_ONCE:null,DEFINE_MANY:null,OVERRIDE_BASE:null,DEFINE_MANY_MERGED:null}),j=[],y={mixins:x.DEFINE_MANY,statics:x.DEFINE_MANY,propTypes:x.DEFINE_MANY,contextTypes:x.DEFINE_MANY,childContextTypes:x.DEFINE_MANY,getDefaultProps:x.DEFINE_MANY_MERGED,getInitialState:x.DEFINE_MANY_MERGED,getChildContext:x.DEFINE_MANY_MERGED,render:x.DEFINE_ONCE,componentWillMount:x.DEFINE_MANY,componentDidMount:x.DEFINE_MANY,componentWillReceiveProps:x.DEFINE_MANY,shouldComponentUpdate:x.DEFINE_ONCE,componentWillUpdate:x.DEFINE_MANY,componentDidUpdate:x.DEFINE_MANY,componentWillUnmount:x.DEFINE_MANY,updateComponent:x.OVERRIDE_BASE},u={displayName:function(A,M){A.displayName=M},mixins:function(A,M){if(M)for(var t=0;t"+T+""},receiveComponent:function(A,M){if(A!==this._currentElement){this._currentElement=A;var t=""+A;if(t!==this._stringText){this._stringText=t;var g=i.getNode(this._rootNodeID);I.updateTextContent(g,t)}}},unmountComponent:function(){e.unmountIDFromEnvironment(this._rootNodeID)}}),A.exports=o},function(A,M,t){"use strict";function I(){this.reinitializeTransaction()}var g=t(18),e=t(73),i=t(4),T=t(20),E={initialize:T,close:function(){C.isBatchingUpdates=!1}},N={initialize:T,close:g.flushBatchedUpdates.bind(g)},o=[N,E];i(I.prototype,e.Mixin,{getTransactionWrappers:function(){return o}});var n=new I,C={isBatchingUpdates:!1,batchedUpdates:function(A,M,t,I,g,e){var i=C.isBatchingUpdates;C.isBatchingUpdates=!0,i?A(M,t,I,g,e):n.perform(A,null,M,t,I,g,e)}};A.exports=C},function(A,M,t){"use strict";function I(){if(!l){l=!0,Q.EventEmitter.injectReactEventListener(B),Q.EventPluginHub.injectEventPluginOrder(T),Q.EventPluginHub.injectInstanceHandle(r),Q.EventPluginHub.injectMount(s),Q.EventPluginHub.injectEventPluginsByName({SimpleEventPlugin:u,EnterLeaveEventPlugin:E,ChangeEventPlugin:e,SelectEventPlugin:j,BeforeInputEventPlugin:g}),Q.NativeComponent.injectGenericComponentClass(D),Q.NativeComponent.injectTextComponentClass(a),Q.Class.injectMixin(n),Q.DOMProperty.injectDOMPropertyConfig(o),Q.DOMProperty.injectDOMPropertyConfig(w),Q.EmptyComponent.injectEmptyComponent("noscript"),Q.Updates.injectReconcileTransaction(x),Q.Updates.injectBatchingStrategy(c),Q.RootIndex.injectCreateReactRootIndex(N.canUseDOM?i.createReactRootIndex:y.createReactRootIndex),Q.Component.injectEnvironment(C)}}var g=t(429),e=t(431),i=t(432),T=t(434),E=t(435),N=t(10),o=t(438),n=t(440),C=t(109),c=t(210),D=t(444),a=t(209),B=t(452),Q=t(453),r=t(48),s=t(12),x=t(457),j=t(463),y=t(464),u=t(465),w=t(462),l=!1;A.exports={inject:I}},function(A,M,t){"use strict";function I(){if(n.current){var A=n.current.getName();if(A)return" Check the render method of ` + "`" + `"+A+"` + "`" + `."}return""}function g(A,M){if(A._store&&!A._store.validated&&null==A.key){A._store.validated=!0;e("uniqueKey",A,M)}}function e(A,M,t){var g=I();if(!g){var e="string"==typeof t?t:t.displayName||t.name;e&&(g=" Check the top-level render call using <"+e+">.")}var i=D[A]||(D[A]={});if(i[g])return null;i[g]=!0;var T={parentOrOwner:g,url:" See https://fb.me/react-warning-keys for more information.",childOwner:null};return M&&M._owner&&M._owner!==n.current&&(T.childOwner=" It was passed a child from "+M._owner.getName()+"."),T}function i(A,M){if("object"==typeof A)if(Array.isArray(A))for(var t=0;t/,e={CHECKSUM_ATTR_NAME:"data-react-checksum",addChecksumToMarkup:function(A){var M=I(A);return A.replace(g," "+e.CHECKSUM_ATTR_NAME+'="'+M+'"$&')},canReuseMarkup:function(A,M){var t=M.getAttribute(e.CHECKSUM_ATTR_NAME);t=t&&parseInt(t,10);var g=I(A);return g===t}};A.exports=e},function(A,M,t){"use strict";var I=t(59),g=I({INSERT_MARKUP:null,MOVE_EXISTING:null,REMOVE_NODE:null,SET_MARKUP:null,TEXT_CONTENT:null});A.exports=g},function(A,M,t){"use strict";function I(A){if("function"==typeof A.type)return A.type;var M=A.type,t=n[M];return null==t&&(n[M]=t=N(M)),t}function g(A){return o?void 0:E(!1),new o(A.type,A.props)}function e(A){return new C(A)}function i(A){return A instanceof C}var T=t(4),E=t(2),N=null,o=null,n={},C=null,c={injectGenericComponentClass:function(A){o=A},injectTextComponentClass:function(A){C=A},injectComponentClasses:function(A){T(n,A)}},D={getComponentClassForElement:I,createInternalComponent:g,createInstanceForText:e,isTextComponent:i,injection:c};A.exports=D},function(A,M,t){"use strict";function I(A,M){}var g=(t(3),{isMounted:function(A){return!1},enqueueCallback:function(A,M){},enqueueForceUpdate:function(A){I(A,"forceUpdate")},enqueueReplaceState:function(A,M){I(A,"replaceState")},enqueueSetState:function(A,M){I(A,"setState")},enqueueSetProps:function(A,M){I(A,"setProps")},enqueueReplaceProps:function(A,M){I(A,"replaceProps")}});A.exports=g},function(A,M,t){"use strict";function I(A){function M(M,t,I,g,e,i){if(g=g||y,i=i||I,null==t[I]){var T=s[e];return M?new Error("Required "+T+" ` + "`" + `"+i+"` + "`" + ` was not specified in "+("` + "`" + `"+g+"` + "`" + `.")):null}return A(t,I,g,e,i)}var t=M.bind(null,!1);return t.isRequired=M.bind(null,!0),t}function g(A){function M(M,t,I,g,e){var i=M[t],T=a(i);if(T!==A){var E=s[g],N=B(i);return new Error("Invalid "+E+" ` + "`" + `"+e+"` + "`" + ` of type "+("` + "`" + `"+N+"` + "`" + ` supplied to ` + "`" + `"+I+"` + "`" + `, expected ")+("` + "`" + `"+A+"` + "`" + `."))}return null}return I(M)}function e(){return I(x.thatReturns(null))}function i(A){function M(M,t,I,g,e){var i=M[t];if(!Array.isArray(i)){var T=s[g],E=a(i);return new Error("Invalid "+T+" ` + "`" + `"+e+"` + "`" + ` of type "+("` + "`" + `"+E+"` + "`" + ` supplied to ` + "`" + `"+I+"` + "`" + `, expected an array."))}for(var N=0;N>"}var r=t(13),s=t(70),x=t(20),j=t(118),y="<>",u={array:g("array"),bool:g("boolean"),func:g("function"),number:g("number"),object:g("object"),string:g("string"),any:e(),arrayOf:i,element:T(),instanceOf:E,node:C(),objectOf:o,oneOf:N,oneOfType:n,shape:c};A.exports=u},function(A,M){"use strict";var t={injectCreateReactRootIndex:function(A){I.createReactRootIndex=A}},I={createReactRootIndex:null,injection:t};A.exports=I},function(A,M){"use strict";var t={currentScrollLeft:0,currentScrollTop:0,refreshScrollValues:function(A){t.currentScrollLeft=A.x,t.currentScrollTop=A.y}};A.exports=t},function(A,M,t){"use strict";function I(A,M){if(null==M?g(!1):void 0,null==A)return M;var t=Array.isArray(A),I=Array.isArray(M);return t&&I?(A.push.apply(A,M),A):t?(A.push(M),A):I?[A].concat(M):[A,M]}var g=t(2);A.exports=I},function(A,M){"use strict";var t=function(A,M,t){Array.isArray(A)?A.forEach(M,t):A&&M.call(t,A)};A.exports=t},function(A,M,t){"use strict";function I(){return!e&&g.canUseDOM&&(e="textContent"in document.documentElement?"textContent":"innerText"),e}var g=t(10),e=null;A.exports=I},function(A,M){"use strict";function t(A){var M=A&&A.nodeName&&A.nodeName.toLowerCase();return M&&("input"===M&&I[A.type]||"textarea"===M)}var I={color:!0,date:!0,datetime:!0,"datetime-local":!0,email:!0,month:!0,number:!0,password:!0,range:!0,search:!0,tel:!0,text:!0,time:!0,url:!0,week:!0};A.exports=t},function(A,M,t){"use strict";function I(A){return A&&A.__esModule?A:{default:A}}function g(){for(var A=arguments.length,M=Array(A),t=0;t=0&&s.splice(M,1)}function T(A){var M=document.createElement("style");return M.type="text/css",e(A,M),M}function E(A){var M=document.createElement("link");return M.rel="stylesheet",e(A,M),M}function N(A,M){var t,I,g;if(M.singleton){var e=r++;t=Q||(Q=T(M)),I=o.bind(null,t,e,!1),g=o.bind(null,t,e,!0)}else A.sourceMap&&"function"==typeof URL&&"function"==typeof URL.createObjectURL&&"function"==typeof URL.revokeObjectURL&&"function"==typeof Blob&&"function"==typeof btoa?(t=E(M),I=C.bind(null,t),g=function(){i(t),t.href&&URL.revokeObjectURL(t.href)}):(t=T(M),I=n.bind(null,t),g=function(){i(t)});return I(A),function(M){if(M){if(M.css===A.css&&M.media===A.media&&M.sourceMap===A.sourceMap)return;I(A=M)}else g()}}function o(A,M,t,I){var g=t?"":I.css;if(A.styleSheet)A.styleSheet.cssText=x(M,g);else{var e=document.createTextNode(g),i=A.childNodes;i[M]&&A.removeChild(i[M]),i.length?A.insertBefore(e,i[M]):A.appendChild(e)}}function n(A,M){var t=M.css,I=M.media;if(I&&A.setAttribute("media",I),A.styleSheet)A.styleSheet.cssText=t;else{for(;A.firstChild;)A.removeChild(A.firstChild);A.appendChild(document.createTextNode(t))}}function C(A,M){var t=M.css,I=M.sourceMap;I&&(t+="\n/*# sourceMappingURL=data:application/json;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(I))))+" */");var g=new Blob([t],{type:"text/css"}),e=A.href;A.href=URL.createObjectURL(g),e&&URL.revokeObjectURL(e)}var c={},D=function(A){var M;return function(){return"undefined"==typeof M&&(M=A.apply(this,arguments)),M}},a=D(function(){return/msie [6-9]\b/.test(window.navigator.userAgent.toLowerCase())}),B=D(function(){return document.head||document.getElementsByTagName("head")[0]}),Q=null,r=0,s=[];A.exports=function(A,M){M=M||{},"undefined"==typeof M.singleton&&(M.singleton=a()),"undefined"==typeof M.insertAt&&(M.insertAt="bottom");var t=g(A);return I(t,M),function(A){for(var e=[],i=0;i-1})))}},{key:"listObjects",value:function(){var A=this.props.dispatch;A(eA.listObjects())}},{key:"selectPrefix",value:function(A,M){A.preventDefault();var t=this.props,I=(t.dispatch,t.currentPath),g=(t.web,t.currentBucket),e=encodeURI(M);if(M.endsWith("/")||""===M){if(M===I)return;D.default.push(TA.pathJoin(g,e))}else window.location=window.location.origin+"/minio/download/"+g+"/"+e+"?token="+DA.default.getItem("token")}},{key:"makeBucket",value:function(A){A.preventDefault();var M=this.refs.makeBucketRef.value;this.refs.makeBucketRef.value="";var t=this.props,I=t.web,g=t.dispatch;this.hideMakeBucketModal(),I.MakeBucket({bucketName:M}).then(function(){g(eA.addBucket(M)),g(eA.selectBucket(M))}).catch(function(A){return g(eA.showAlert({type:"danger",message:A.message}))})}},{key:"hideMakeBucketModal",value:function(){var A=this.props.dispatch;A(eA.hideMakeBucketModal())}},{key:"showMakeBucketModal",value:function(A){A.preventDefault();var M=this.props.dispatch;M(eA.showMakeBucketModal())}},{key:"showAbout",value:function(A){A.preventDefault();var M=this.props.dispatch;M(eA.showAbout())}},{key:"hideAbout",value:function(A){A.preventDefault();var M=this.props.dispatch;M(eA.hideAbout())}},{key:"showBucketPolicy",value:function(A){A.preventDefault();var M=this.props.dispatch;M(eA.showBucketPolicy())}},{key:"hideBucketPolicy",value:function(A){A.preventDefault();var M=this.props.dispatch;M(eA.hideBucketPolicy())}},{key:"uploadFile",value:function(A){A.preventDefault();var M=this.props,t=M.dispatch,I=M.buckets;if(0===I.length)return void t(eA.showAlert({type:"danger",message:"Bucket needs to be created before trying to upload files."}));var g=A.target.files[0];A.target.value=null,this.xhr=new XMLHttpRequest,t(eA.uploadFile(g,this.xhr))}},{key:"removeObject",value:function(){var A=this,M=this.props,t=M.web,I=M.dispatch,g=M.currentPath,e=M.currentBucket,i=M.deleteConfirmation,T=M.checkedObjects,E=T.length>0?T:[i.object];t.RemoveObject({bucketname:e,objects:E}).then(function(){if(A.hideDeleteConfirmation(),T.length>0){for(var M=0;M0})},o.default.createElement("span",{className:"la-label"},o.default.createElement("i",{className:"fa fa-check-circle"})," ",c.length," Objects selected"),o.default.createElement("span",{className:"la-actions pull-right"},o.default.createElement("button",{onClick:this.downloadSelected.bind(this)}," Download all as zip ")),o.default.createElement("span",{className:"la-actions pull-right"},o.default.createElement("button",{onClick:this.showDeleteConfirmation.bind(this)}," Delete selected ")),o.default.createElement("i",{className:"la-close fa fa-times",onClick:this.clearSelected.bind(this)})),o.default.createElement(m.default,null,v,o.default.createElement("header",{className:"fe-header-mobile hidden-lg hidden-md"},o.default.createElement("div",{id:"feh-trigger",className:"feh-trigger "+(0,C.default)({"feht-toggled":y}),onClick:this.toggleSidebar.bind(this,!y)},o.default.createElement("div",{className:"feht-lines"},o.default.createElement("div",{className:"top"}),o.default.createElement("div",{className:"center"}),o.default.createElement("div",{className:"bottom"}))),o.default.createElement("img",{className:"mh-logo",src:IA.default,alt:""})),o.default.createElement("header",{className:"fe-header"},o.default.createElement(G.default,{selectPrefix:this.selectPrefix.bind(this)}),AA,o.default.createElement("ul",{className:"feh-actions"},o.default.createElement(b.default,null),K,_)),o.default.createElement("div",{className:"feb-container"},o.default.createElement("header",{className:"fesl-row","data-type":"folder"},o.default.createElement("div",{className:"fesl-item fesl-item-icon"}),o.default.createElement("div",{className:"fesl-item fesl-item-name",onClick:this.sortObjectsByName.bind(this),"data-sort":"name"},"Name",o.default.createElement("i",{className:(0,C.default)({"fesli-sort":!0,fa:!0,"fa-sort-alpha-desc":i,"fa-sort-alpha-asc":!i})})),o.default.createElement("div",{className:"fesl-item fesl-item-size",onClick:this.sortObjectsBySize.bind(this),"data-sort":"size"},"Size",o.default.createElement("i",{className:(0,C.default)({"fesli-sort":!0,fa:!0,"fa-sort-amount-desc":T,"fa-sort-amount-asc":!T})})),o.default.createElement("div",{className:"fesl-item fesl-item-modified",onClick:this.sortObjectsByDate.bind(this),"data-sort":"last-modified"},"Last Modified",o.default.createElement("i",{className:(0,C.default)({"fesli-sort":!0,fa:!0,"fa-sort-numeric-desc":E,"fa-sort-numeric-asc":!E})})),o.default.createElement("div",{className:"fesl-item fesl-item-actions"}))),o.default.createElement("div",{className:"feb-container"},o.default.createElement(BA.default,{loadMore:this.listObjects.bind(this),hasMore:J,useWindow:!0,initialLoad:!1},o.default.createElement(f.default,{dataType:this.dataType.bind(this),selectPrefix:this.selectPrefix.bind(this),showDeleteConfirmation:this.showDeleteConfirmation.bind(this),shareObject:this.shareObject.bind(this),checkObject:this.checkObject.bind(this),checkedObjectsArray:c})),o.default.createElement("div",{className:"text-center",style:{display:J&&S?"block":"none"}},o.default.createElement("span",null,"Loading..."))),o.default.createElement(X.default,null),eA,o.default.createElement(s.default,{className:"modal-create-bucket",bsSize:"small",animation:!1,show:g,onHide:this.hideMakeBucketModal.bind(this)},o.default.createElement("button",{className:"close close-alt",onClick:this.hideMakeBucketModal.bind(this)},o.default.createElement("span",null,"×")),o.default.createElement(j.default,null,o.default.createElement("form",{onSubmit:this.makeBucket.bind(this)},o.default.createElement("div",{className:"input-group"},o.default.createElement("input",{className:"ig-text",type:"text",ref:"makeBucketRef",placeholder:"Bucket Name",autoFocus:!0}),o.default.createElement("i",{className:"ig-helpers"}))))),o.default.createElement(s.default,{className:"modal-about modal-dark",animation:!1,show:N,onHide:this.hideAbout.bind(this)},o.default.createElement("button",{className:"close",onClick:this.hideAbout.bind(this)},o.default.createElement("span",null,"×")),o.default.createElement("div",{className:"ma-inner"},o.default.createElement("div",{className:"mai-item hidden-xs"},o.default.createElement("a",{href:"https://minio.io",target:"_blank"},o.default.createElement("img",{className:"maii-logo",src:IA.default,alt:""}))),o.default.createElement("div",{className:"mai-item"},o.default.createElement("ul",{className:"maii-list"},o.default.createElement("li",null,o.default.createElement("div",null,"Version"),o.default.createElement("small",null,a)),o.default.createElement("li",null,o.default.createElement("div",null,"Memory"),o.default.createElement("small",null,Q)),o.default.createElement("li",null,o.default.createElement("div",null,"Platform"),o.default.createElement("small",null,r)),o.default.createElement("li",null,o.default.createElement("div",null,"Runtime"),o.default.createElement("small",null,x)))))),o.default.createElement(s.default,{className:"modal-policy",animation:!1,show:n,onHide:this.hideBucketPolicy.bind(this)},o.default.createElement(u.default,null,"Bucket Policy (",S,")",o.default.createElement("button",{className:"close close-alt",onClick:this.hideBucketPolicy.bind(this)},o.default.createElement("span",null,"×"))),o.default.createElement("div",{className:"pm-body"},o.default.createElement(Z.default,{bucket:S}),d.map(function(A,M){return o.default.createElement(q.default,{key:M,prefix:A.prefix,policy:A.policy})}))),o.default.createElement(MA.default,{show:p.show,icon:"fa fa-exclamation-triangle mci-red",text:"Are you sure you want to delete?",sub:"This cannot be undone!",okText:"Delete",cancelText:"Cancel",okHandler:this.removeObject.bind(this),cancelHandler:this.hideDeleteConfirmation.bind(this)}),o.default.createElement(s.default,{show:U.show,animation:!1,onHide:this.hideShareObjectModal.bind(this),bsSize:"small"},o.default.createElement(u.default,null,"Share Object"),o.default.createElement(j.default,null,o.default.createElement("div",{className:"input-group copy-text"},o.default.createElement("label",null,"Shareable Link"),o.default.createElement("input",{type:"text",ref:"copyTextInput",readOnly:"readOnly",value:window.location.protocol+"//"+U.url,onClick:this.selectTexts.bind(this)})),o.default.createElement("div",{className:"input-group",style:{display:F.LoggedIn()?"block":"none"}},o.default.createElement("label",null,"Expires in"),o.default.createElement("div",{className:"set-expire"},o.default.createElement("div",{className:"set-expire-item"},o.default.createElement("i",{className:"set-expire-increase",onClick:this.handleExpireValue.bind(this,"expireDays",1,U.object)}),o.default.createElement("div",{className:"set-expire-title"},"Days"),o.default.createElement("div",{className:"set-expire-value"},o.default.createElement("input",{ref:"expireDays",type:"number",min:0,max:7,defaultValue:5})),o.default.createElement("i",{className:"set-expire-decrease",onClick:this.handleExpireValue.bind(this,"expireDays",-1,U.object)})),o.default.createElement("div",{className:"set-expire-item"},o.default.createElement("i",{className:"set-expire-increase",onClick:this.handleExpireValue.bind(this,"expireHours",1,U.object)}),o.default.createElement("div",{className:"set-expire-title"},"Hours"),o.default.createElement("div",{className:"set-expire-value"},o.default.createElement("input",{ref:"expireHours",type:"number",min:0,max:23,defaultValue:0})),o.default.createElement("i",{className:"set-expire-decrease",onClick:this.handleExpireValue.bind(this,"expireHours",-1,U.object)})),o.default.createElement("div",{className:"set-expire-item"},o.default.createElement("i",{className:"set-expire-increase",onClick:this.handleExpireValue.bind(this,"expireMins",1,U.object)}),o.default.createElement("div",{className:"set-expire-title"},"Minutes"),o.default.createElement("div",{className:"set-expire-value"},o.default.createElement("input",{ref:"expireMins",type:"number",min:0,max:59,defaultValue:0})),o.default.createElement("i",{className:"set-expire-decrease",onClick:this.handleExpireValue.bind(this,"expireMins",-1,U.object)}))))),o.default.createElement("div",{className:"modal-footer"},o.default.createElement(CA.default,{text:window.location.protocol+"//"+U.url,onCopy:this.showMessage.bind(this)},o.default.createElement("button",{className:"btn btn-success"},"Copy Link")),o.default.createElement("button",{className:"btn btn-link",onClick:this.hideShareObjectModal.bind(this)},"Cancel"))),H)))}}]),M}(o.default.Component);M.default=QA},function(A,M,t){"use strict";function I(A){return A&&A.__esModule?A:{default:A}}Object.defineProperty(M,"__esModule",{value:!0});var g=t(1),e=I(g),i=t(22),T=I(i),E=t(91),N=I(E),o=function(A){var M=A.fullScreenFunc,t=A.aboutFunc,I=A.settingsFunc,g=A.logoutFunc;return e.default.createElement("li",null,e.default.createElement(N.default,{pullRight:!0,id:"top-right-menu"},e.default.createElement(N.default.Toggle,{noCaret:!0},e.default.createElement("i",{className:"fa fa-reorder"})),e.default.createElement(N.default.Menu,{className:"dropdown-menu-right"},e.default.createElement("li",null,e.default.createElement("a",{target:"_blank",href:"https://github.com/minio/minio"},"Github ",e.default.createElement("i",{className:"fa fa-github"}))),e.default.createElement("li",null,e.default.createElement("a",{href:"",onClick:M},"Fullscreen ",e.default.createElement("i",{className:"fa fa-expand"}))),e.default.createElement("li",null,e.default.createElement("a",{target:"_blank",href:"https://docs.minio.io/"},"Documentation ",e.default.createElement("i",{className:"fa fa-book"}))),e.default.createElement("li",null,e.default.createElement("a",{target:"_blank",href:"https://slack.minio.io"},"Ask for help ",e.default.createElement("i",{className:"fa fa-question-circle"}))),e.default.createElement("li",null,e.default.createElement("a",{href:"",onClick:t},"About ",e.default.createElement("i",{className:"fa fa-info-circle"}))),e.default.createElement("li",null,e.default.createElement("a",{href:"",onClick:I},"Settings ",e.default.createElement("i",{className:"fa fa-cog"}))),e.default.createElement("li",null,e.default.createElement("a",{href:"",onClick:g},"Sign Out ",e.default.createElement("i",{className:"fa fa-sign-out"}))))))};M.default=(0,T.default)(function(A){return A})(o)},function(A,M,t){"use strict";function I(A){return A&&A.__esModule?A:{default:A}}Object.defineProperty(M,"__esModule",{value:!0});var g=t(1),e=I(g),i=t(22),T=I(i),E=t(95),N=I(E),o=t(94),n=I(o),C=function(A){var M=A.latestUiVersion;return M===currentUiVersion?e.default.createElement("noscript",null):e.default.createElement("li",{className:"hidden-xs hidden-sm"},e.default.createElement("a",{href:""},e.default.createElement(n.default,{placement:"left",overlay:e.default.createElement(N.default,{id:"tt-version-update"},"New update available. Click to refresh.")}," ",e.default.createElement("i",{className:"fa fa-refresh"})," ")))};M.default=(0,T.default)(function(A){return{latestUiVersion:A.latestUiVersion}})(C)},function(A,M,t){"use strict";function I(A){if(A&&A.__esModule)return A;var M={};if(null!=A)for(var t in A)Object.prototype.hasOwnProperty.call(A,t)&&(M[t]=A[t]);return M.default=A,M}function g(A){return A&&A.__esModule?A:{default:A}}function e(A,M){if(!(A instanceof M))throw new TypeError("Cannot call a class as a function")}function i(A,M){if(!A)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!M||"object"!=typeof M&&"function"!=typeof M?A:M}function T(A,M){if("function"!=typeof M&&null!==M)throw new TypeError("Super expression must either be null or a function, not "+typeof M);A.prototype=Object.create(M&&M.prototype,{constructor:{value:A,enumerable:!1,writable:!0,configurable:!0}}),M&&(Object.setPrototypeOf?Object.setPrototypeOf(A,M):A.__proto__=M)}Object.defineProperty(M,"__esModule",{value:!0});var E=function(){function A(A,M){for(var t=0;t0&&(A.name.endsWith("/")||(Q=e.default.createElement(c.default,{id:"fia-dropdown-"+A.name.replace(".","-")},e.default.createElement(c.default.Toggle,{noCaret:!0,className:"fia-toggle"}),e.default.createElement(c.default.Menu,null,e.default.createElement("a",{href:"",className:"fiad-action",onClick:function(M){return E(M,""+t+A.name)}},e.default.createElement("i",{className:"fa fa-copy"})),r))));var s="",x="";return C.indexOf(A.name)>-1&&(s=" fesl-row-selected",x=!0),e.default.createElement("div",{key:M,className:"fesl-row "+B+s,"data-type":g(A.name,A.contentType)},e.default.createElement("div",{className:"fesl-item fesl-item-icon"},e.default.createElement("div",{className:"fi-select"},e.default.createElement("input",{type:"checkbox",name:A.name,checked:x,onChange:function(M){return n(M,A.name)}}),e.default.createElement("i",{className:"fis-icon"}),e.default.createElement("i",{className:"fis-helper"}))),e.default.createElement("div",{className:"fesl-item fesl-item-name"},e.default.createElement("a",{href:"",onClick:function(M){return I(M,""+t+A.name)}},A.name)),e.default.createElement("div",{className:"fesl-item fesl-item-size"},D),e.default.createElement("div",{className:"fesl-item fesl-item-modified"},a),e.default.createElement("div",{className:"fesl-item fesl-item-actions"},Q))});return e.default.createElement("div",null,D)};M.default=(0,n.default)(function(A){return{objects:A.objects,currentPath:A.currentPath,loadPath:A.loadPath}})(D)},function(A,M,t){"use strict";function I(A){return A&&A.__esModule?A:{default:A}}Object.defineProperty(M,"__esModule",{value:!0});var g=t(1),e=I(g),i=t(22),T=I(i),E=function(A){var M=A.currentBucket,t=A.currentPath,I=A.selectPrefix,g=[],i="";return t&&(i=t.split("/").map(function(A,M){g.push(A);var t=g.join("/")+"/";return e.default.createElement("span",{key:M},e.default.createElement("a",{href:"",onClick:function(A){return I(A,t)}},A))})),e.default.createElement("h2",null,e.default.createElement("span",{className:"main"},e.default.createElement("a",{onClick:function(A){return I(A,"")},href:""},M)),i)};M.default=(0,T.default)(function(A){return{currentBucket:A.currentBucket,currentPath:A.currentPath}})(E)},function(A,M,t){"use strict";function I(A){if(A&&A.__esModule)return A;var M={};if(null!=A)for(var t in A)Object.prototype.hasOwnProperty.call(A,t)&&(M[t]=A[t]);return M.default=A,M}function g(A){return A&&A.__esModule?A:{default:A}}function e(A,M){if(!(A instanceof M))throw new TypeError("Cannot call a class as a function")}function i(A,M){if(!A)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!M||"object"!=typeof M&&"function"!=typeof M?A:M}function T(A,M){if("function"!=typeof M&&null!==M)throw new TypeError("Super expression must either be null or a function, not "+typeof M);A.prototype=Object.create(M&&M.prototype,{constructor:{value:A,enumerable:!1,writable:!0,configurable:!0}}),M&&(Object.setPrototypeOf?Object.setPrototypeOf(A,M):A.__proto__=M)}Object.defineProperty(M,"__esModule",{value:!0});var E=function(){function A(A,M){for(var t=0;t0&&void 0!==arguments[0]?arguments[0]:{buckets:[],visibleBuckets:[],objects:[],istruncated:!0,storageInfo:{},serverInfo:{},currentBucket:"",currentPath:"",showMakeBucketModal:!1,uploads:{},alert:{show:!1,type:"danger",message:""},loginError:!1,sortNameOrder:!1,sortSizeOrder:!1,sortDateOrder:!1,latestUiVersion:currentUiVersion,sideBarActive:!1,loginRedirectPath:E.minioBrowserPrefix,settings:{accessKey:"",secretKey:"",secretKeyVisible:!1},showSettings:!1,policies:[],deleteConfirmation:{object:"",show:!1},shareObject:{show:!1,url:"",object:""},prefixWritable:!1,checkedObjects:[]},M=arguments[1],t=Object.assign({},A);switch(M.type){case T.SET_WEB:t.web=M.web;break;case T.SET_BUCKETS:t.buckets=M.buckets;break;case T.ADD_BUCKET:t.buckets=[M.bucket].concat(e(t.buckets)),t.visibleBuckets=[M.bucket].concat(e(t.visibleBuckets));break;case T.SET_VISIBLE_BUCKETS:t.visibleBuckets=M.visibleBuckets;break;case T.SET_CURRENT_BUCKET:t.currentBucket=M.currentBucket;break;case T.SET_OBJECTS:M.objects.length?(t.objects=[].concat(e(M.objects)),t.marker=M.marker,t.istruncated=M.istruncated):(t.objects=[],t.marker="",t.istruncated=M.istruncated);break;case T.SET_CURRENT_PATH:t.currentPath=M.currentPath;break;case T.SET_STORAGE_INFO:t.storageInfo=M.storageInfo;break;case T.SET_SERVER_INFO:t.serverInfo=M.serverInfo;break;case T.SHOW_MAKEBUCKET_MODAL:t.showMakeBucketModal=M.showMakeBucketModal;break;case T.UPLOAD_PROGRESS:t.uploads=Object.assign({},t.uploads),t.uploads[M.slug].loaded=M.loaded;break;case T.ADD_UPLOAD:t.uploads=Object.assign({},t.uploads,g({},M.slug,{loaded:0,size:M.size,xhr:M.xhr,name:M.name}));break;case T.STOP_UPLOAD:t.uploads=Object.assign({},t.uploads),delete t.uploads[M.slug];break;case T.SET_ALERT:t.alert.alertTimeout&&clearTimeout(t.alert.alertTimeout),M.alert.show?t.alert=M.alert:t.alert=Object.assign({},t.alert,{show:!1});break;case T.SET_LOGIN_ERROR:t.loginError=!0;break;case T.SET_SHOW_ABORT_MODAL:t.showAbortModal=M.showAbortModal;break;case T.SHOW_ABOUT:t.showAbout=M.showAbout;break;case T.SET_SORT_NAME_ORDER:t.sortNameOrder=M.sortNameOrder;break;case T.SET_SORT_SIZE_ORDER:t.sortSizeOrder=M.sortSizeOrder;break;case T.SET_SORT_DATE_ORDER:t.sortDateOrder=M.sortDateOrder;break;case T.SET_LATEST_UI_VERSION:t.latestUiVersion=M.latestUiVersion;break;case T.SET_SIDEBAR_STATUS:t.sidebarStatus=M.sidebarStatus;break;case T.SET_LOGIN_REDIRECT_PATH:t.loginRedirectPath=M.path;case T.SET_LOAD_BUCKET:t.loadBucket=M.loadBucket;break;case T.SET_LOAD_PATH:t.loadPath=M.loadPath;break;case T.SHOW_SETTINGS:t.showSettings=M.showSettings;break;case T.SET_SETTINGS:t.settings=Object.assign({},t.settings,M.settings);break;case T.SHOW_BUCKET_POLICY:t.showBucketPolicy=M.showBucketPolicy;break;case T.SET_POLICIES:t.policies=M.policies;break;case T.DELETE_CONFIRMATION:t.deleteConfirmation=Object.assign({},M.payload);break;case T.SET_SHARE_OBJECT:t.shareObject=Object.assign({},M.shareObject);break;case T.SET_PREFIX_WRITABLE:t.prefixWritable=M.prefixWritable;break;case T.REMOVE_OBJECT:var I=t.objects.findIndex(function(A){return A.name===M.object});if(I==-1)break;t.objects=[].concat(e(t.objects.slice(0,I)),e(t.objects.slice(I+1)));break;case T.CHECKED_OBJECTS_ADD:t.checkedObjects=[].concat(e(t.checkedObjects),[M.objectName]);break;case T.CHECKED_OBJECTS_REMOVE:var i=t.checkedObjects.indexOf(M.objectName);if(i==-1)break;t.checkedObjects=[].concat(e(t.checkedObjects.slice(0,i)),e(t.checkedObjects.slice(i+1)));break;case T.CHECKED_OBJECTS_RESET:t.checkedObjects=[]}return t}},function(A,M,t){"use strict";function I(A){if(Array.isArray(A)){for(var M=0,t=Array(A.length);MM.name.toLowerCase()?1:0}),g=g.sort(function(A,M){return A.name.toLowerCase()M.name.toLowerCase()?1:0}),M&&(t=t.reverse(),g=g.reverse()),[].concat(I(t),I(g))},M.sortObjectsBySize=function(A,M){var t=A.filter(function(A){return A.name.endsWith("/")}),g=A.filter(function(A){return!A.name.endsWith("/")});return g=g.sort(function(A,M){return A.size-M.size}),M&&(g=g.reverse()),[].concat(I(t),I(g))},M.sortObjectsByDate=function(A,M){var t=A.filter(function(A){return A.name.endsWith("/")}),g=A.filter(function(A){return!A.name.endsWith("/")});return g=g.sort(function(A,M){return new Date(A.lastModified).getTime()-new Date(M.lastModified).getTime()}),M&&(g=g.reverse()),[].concat(I(t),I(g))},M.pathSlice=function(A){A=A.replace(g.minioBrowserPrefix,"");var M="",t="";if(!A)return{bucket:t,prefix:M};var I=A.indexOf("/",1);return I==-1?(t=A.slice(1),{bucket:t,prefix:M}):(t=A.slice(1,I),M=A.slice(I+1),{bucket:t,prefix:M})},M.pathJoin=function(A,M){return M||(M=""),g.minioBrowserPrefix+"/"+A+"/"+M}},function(A,M,t){"use strict";function I(A){if(A&&A.__esModule)return A;var M={};if(null!=A)for(var t in A)Object.prototype.hasOwnProperty.call(A,t)&&(M[t]=A[t]);return M.default=A,M}function g(A){return A&&A.__esModule?A:{default:A}}function e(A,M){if(!(A instanceof M))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(M,"__esModule",{value:!0});var i=function(){function A(A,M){for(var t=0;tE;)for(var C,c=e(i[E++]),D=o?N(c).concat(o(c)):N(c),a=D.length,B=0;a>B;)n.call(c,C=D[B++])&&(t[C]=c[C]);return t}:Object.assign},function(A,M,t){var I=t(79),g=t(49),e=t(133);A.exports=function(A,M){var t=(g.Object||{})[A]||Object[A],i={};i[A]=M(t),I(I.S+I.F*e(function(){t(1)}),"Object",i)}},function(A,M,t){var I=t(80).getDesc,g=t(134),e=t(261),i=function(A,M){if(e(A),!g(M)&&null!==M)throw TypeError(M+": can't set as prototype!")};A.exports={set:Object.setPrototypeOf||("__proto__"in{}?function(A,M,g){try{g=t(132)(Function.call,I(Object.prototype,"__proto__").set,2),g(A,[]),M=!(A instanceof Array)}catch(A){M=!0}return function(A,t){return i(A,t),M?A.__proto__=t:g(A,t),A}}({},!1):void 0),check:i}},function(A,M,t){var I=t(79);I(I.S+I.F,"Object",{assign:t(266)})},function(A,M,t){var I=t(135);t(267)("keys",function(A){return function(M){return A(I(M)); +}})},function(A,M,t){var I=t(79);I(I.S,"Object",{setPrototypeOf:t(268).set})},function(A,M,t){function I(A){if(A)return g(A)}function g(A){for(var M in I.prototype)A[M]=I.prototype[M];return A}A.exports=I,I.prototype.on=I.prototype.addEventListener=function(A,M){return this._callbacks=this._callbacks||{},(this._callbacks["$"+A]=this._callbacks["$"+A]||[]).push(M),this},I.prototype.once=function(A,M){function t(){this.off(A,t),M.apply(this,arguments)}return t.fn=M,this.on(A,t),this},I.prototype.off=I.prototype.removeListener=I.prototype.removeAllListeners=I.prototype.removeEventListener=function(A,M){if(this._callbacks=this._callbacks||{},0==arguments.length)return this._callbacks={},this;var t=this._callbacks["$"+A];if(!t)return this;if(1==arguments.length)return delete this._callbacks["$"+A],this;for(var I,g=0;gli{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.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 infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-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{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:"\\F000"}.fa-music:before{content:"\\F001"}.fa-search:before{content:"\\F002"}.fa-envelope-o:before{content:"\\F003"}.fa-heart:before{content:"\\F004"}.fa-star:before{content:"\\F005"}.fa-star-o:before{content:"\\F006"}.fa-user:before{content:"\\F007"}.fa-film:before{content:"\\F008"}.fa-th-large:before{content:"\\F009"}.fa-th:before{content:"\\F00A"}.fa-th-list:before{content:"\\F00B"}.fa-check:before{content:"\\F00C"}.fa-close:before,.fa-remove:before,.fa-times:before{content:"\\F00D"}.fa-search-plus:before{content:"\\F00E"}.fa-search-minus:before{content:"\\F010"}.fa-power-off:before{content:"\\F011"}.fa-signal:before{content:"\\F012"}.fa-cog:before,.fa-gear:before{content:"\\F013"}.fa-trash-o:before{content:"\\F014"}.fa-home:before{content:"\\F015"}.fa-file-o:before{content:"\\F016"}.fa-clock-o:before{content:"\\F017"}.fa-road:before{content:"\\F018"}.fa-download:before{content:"\\F019"}.fa-arrow-circle-o-down:before{content:"\\F01A"}.fa-arrow-circle-o-up:before{content:"\\F01B"}.fa-inbox:before{content:"\\F01C"}.fa-play-circle-o:before{content:"\\F01D"}.fa-repeat:before,.fa-rotate-right:before{content:"\\F01E"}.fa-refresh:before{content:"\\F021"}.fa-list-alt:before{content:"\\F022"}.fa-lock:before{content:"\\F023"}.fa-flag:before{content:"\\F024"}.fa-headphones:before{content:"\\F025"}.fa-volume-off:before{content:"\\F026"}.fa-volume-down:before{content:"\\F027"}.fa-volume-up:before{content:"\\F028"}.fa-qrcode:before{content:"\\F029"}.fa-barcode:before{content:"\\F02A"}.fa-tag:before{content:"\\F02B"}.fa-tags:before{content:"\\F02C"}.fa-book:before{content:"\\F02D"}.fa-bookmark:before{content:"\\F02E"}.fa-print:before{content:"\\F02F"}.fa-camera:before{content:"\\F030"}.fa-font:before{content:"\\F031"}.fa-bold:before{content:"\\F032"}.fa-italic:before{content:"\\F033"}.fa-text-height:before{content:"\\F034"}.fa-text-width:before{content:"\\F035"}.fa-align-left:before{content:"\\F036"}.fa-align-center:before{content:"\\F037"}.fa-align-right:before{content:"\\F038"}.fa-align-justify:before{content:"\\F039"}.fa-list:before{content:"\\F03A"}.fa-dedent:before,.fa-outdent:before{content:"\\F03B"}.fa-indent:before{content:"\\F03C"}.fa-video-camera:before{content:"\\F03D"}.fa-image:before,.fa-photo:before,.fa-picture-o:before{content:"\\F03E"}.fa-pencil:before{content:"\\F040"}.fa-map-marker:before{content:"\\F041"}.fa-adjust:before{content:"\\F042"}.fa-tint:before{content:"\\F043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\\F044"}.fa-share-square-o:before{content:"\\F045"}.fa-check-square-o:before{content:"\\F046"}.fa-arrows:before{content:"\\F047"}.fa-step-backward:before{content:"\\F048"}.fa-fast-backward:before{content:"\\F049"}.fa-backward:before{content:"\\F04A"}.fa-play:before{content:"\\F04B"}.fa-pause:before{content:"\\F04C"}.fa-stop:before{content:"\\F04D"}.fa-forward:before{content:"\\F04E"}.fa-fast-forward:before{content:"\\F050"}.fa-step-forward:before{content:"\\F051"}.fa-eject:before{content:"\\F052"}.fa-chevron-left:before{content:"\\F053"}.fa-chevron-right:before{content:"\\F054"}.fa-plus-circle:before{content:"\\F055"}.fa-minus-circle:before{content:"\\F056"}.fa-times-circle:before{content:"\\F057"}.fa-check-circle:before{content:"\\F058"}.fa-question-circle:before{content:"\\F059"}.fa-info-circle:before{content:"\\F05A"}.fa-crosshairs:before{content:"\\F05B"}.fa-times-circle-o:before{content:"\\F05C"}.fa-check-circle-o:before{content:"\\F05D"}.fa-ban:before{content:"\\F05E"}.fa-arrow-left:before{content:"\\F060"}.fa-arrow-right:before{content:"\\F061"}.fa-arrow-up:before{content:"\\F062"}.fa-arrow-down:before{content:"\\F063"}.fa-mail-forward:before,.fa-share:before{content:"\\F064"}.fa-expand:before{content:"\\F065"}.fa-compress:before{content:"\\F066"}.fa-plus:before{content:"\\F067"}.fa-minus:before{content:"\\F068"}.fa-asterisk:before{content:"\\F069"}.fa-exclamation-circle:before{content:"\\F06A"}.fa-gift:before{content:"\\F06B"}.fa-leaf:before{content:"\\F06C"}.fa-fire:before{content:"\\F06D"}.fa-eye:before{content:"\\F06E"}.fa-eye-slash:before{content:"\\F070"}.fa-exclamation-triangle:before,.fa-warning:before{content:"\\F071"}.fa-plane:before{content:"\\F072"}.fa-calendar:before{content:"\\F073"}.fa-random:before{content:"\\F074"}.fa-comment:before{content:"\\F075"}.fa-magnet:before{content:"\\F076"}.fa-chevron-up:before{content:"\\F077"}.fa-chevron-down:before{content:"\\F078"}.fa-retweet:before{content:"\\F079"}.fa-shopping-cart:before{content:"\\F07A"}.fa-folder:before{content:"\\F07B"}.fa-folder-open:before{content:"\\F07C"}.fa-arrows-v:before{content:"\\F07D"}.fa-arrows-h:before{content:"\\F07E"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\\F080"}.fa-twitter-square:before{content:"\\F081"}.fa-facebook-square:before{content:"\\F082"}.fa-camera-retro:before{content:"\\F083"}.fa-key:before{content:"\\F084"}.fa-cogs:before,.fa-gears:before{content:"\\F085"}.fa-comments:before{content:"\\F086"}.fa-thumbs-o-up:before{content:"\\F087"}.fa-thumbs-o-down:before{content:"\\F088"}.fa-star-half:before{content:"\\F089"}.fa-heart-o:before{content:"\\F08A"}.fa-sign-out:before{content:"\\F08B"}.fa-linkedin-square:before{content:"\\F08C"}.fa-thumb-tack:before{content:"\\F08D"}.fa-external-link:before{content:"\\F08E"}.fa-sign-in:before{content:"\\F090"}.fa-trophy:before{content:"\\F091"}.fa-github-square:before{content:"\\F092"}.fa-upload:before{content:"\\F093"}.fa-lemon-o:before{content:"\\F094"}.fa-phone:before{content:"\\F095"}.fa-square-o:before{content:"\\F096"}.fa-bookmark-o:before{content:"\\F097"}.fa-phone-square:before{content:"\\F098"}.fa-twitter:before{content:"\\F099"}.fa-facebook-f:before,.fa-facebook:before{content:"\\F09A"}.fa-github:before{content:"\\F09B"}.fa-unlock:before{content:"\\F09C"}.fa-credit-card:before{content:"\\F09D"}.fa-feed:before,.fa-rss:before{content:"\\F09E"}.fa-hdd-o:before{content:"\\F0A0"}.fa-bullhorn:before{content:"\\F0A1"}.fa-bell:before{content:"\\F0F3"}.fa-certificate:before{content:"\\F0A3"}.fa-hand-o-right:before{content:"\\F0A4"}.fa-hand-o-left:before{content:"\\F0A5"}.fa-hand-o-up:before{content:"\\F0A6"}.fa-hand-o-down:before{content:"\\F0A7"}.fa-arrow-circle-left:before{content:"\\F0A8"}.fa-arrow-circle-right:before{content:"\\F0A9"}.fa-arrow-circle-up:before{content:"\\F0AA"}.fa-arrow-circle-down:before{content:"\\F0AB"}.fa-globe:before{content:"\\F0AC"}.fa-wrench:before{content:"\\F0AD"}.fa-tasks:before{content:"\\F0AE"}.fa-filter:before{content:"\\F0B0"}.fa-briefcase:before{content:"\\F0B1"}.fa-arrows-alt:before{content:"\\F0B2"}.fa-group:before,.fa-users:before{content:"\\F0C0"}.fa-chain:before,.fa-link:before{content:"\\F0C1"}.fa-cloud:before{content:"\\F0C2"}.fa-flask:before{content:"\\F0C3"}.fa-cut:before,.fa-scissors:before{content:"\\F0C4"}.fa-copy:before,.fa-files-o:before{content:"\\F0C5"}.fa-paperclip:before{content:"\\F0C6"}.fa-floppy-o:before,.fa-save:before{content:"\\F0C7"}.fa-square:before{content:"\\F0C8"}.fa-bars:before,.fa-navicon:before,.fa-reorder:before{content:"\\F0C9"}.fa-list-ul:before{content:"\\F0CA"}.fa-list-ol:before{content:"\\F0CB"}.fa-strikethrough:before{content:"\\F0CC"}.fa-underline:before{content:"\\F0CD"}.fa-table:before{content:"\\F0CE"}.fa-magic:before{content:"\\F0D0"}.fa-truck:before{content:"\\F0D1"}.fa-pinterest:before{content:"\\F0D2"}.fa-pinterest-square:before{content:"\\F0D3"}.fa-google-plus-square:before{content:"\\F0D4"}.fa-google-plus:before{content:"\\F0D5"}.fa-money:before{content:"\\F0D6"}.fa-caret-down:before{content:"\\F0D7"}.fa-caret-up:before{content:"\\F0D8"}.fa-caret-left:before{content:"\\F0D9"}.fa-caret-right:before{content:"\\F0DA"}.fa-columns:before{content:"\\F0DB"}.fa-sort:before,.fa-unsorted:before{content:"\\F0DC"}.fa-sort-desc:before,.fa-sort-down:before{content:"\\F0DD"}.fa-sort-asc:before,.fa-sort-up:before{content:"\\F0DE"}.fa-envelope:before{content:"\\F0E0"}.fa-linkedin:before{content:"\\F0E1"}.fa-rotate-left:before,.fa-undo:before{content:"\\F0E2"}.fa-gavel:before,.fa-legal:before{content:"\\F0E3"}.fa-dashboard:before,.fa-tachometer:before{content:"\\F0E4"}.fa-comment-o:before{content:"\\F0E5"}.fa-comments-o:before{content:"\\F0E6"}.fa-bolt:before,.fa-flash:before{content:"\\F0E7"}.fa-sitemap:before{content:"\\F0E8"}.fa-umbrella:before{content:"\\F0E9"}.fa-clipboard:before,.fa-paste:before{content:"\\F0EA"}.fa-lightbulb-o:before{content:"\\F0EB"}.fa-exchange:before{content:"\\F0EC"}.fa-cloud-download:before{content:"\\F0ED"}.fa-cloud-upload:before{content:"\\F0EE"}.fa-user-md:before{content:"\\F0F0"}.fa-stethoscope:before{content:"\\F0F1"}.fa-suitcase:before{content:"\\F0F2"}.fa-bell-o:before{content:"\\F0A2"}.fa-coffee:before{content:"\\F0F4"}.fa-cutlery:before{content:"\\F0F5"}.fa-file-text-o:before{content:"\\F0F6"}.fa-building-o:before{content:"\\F0F7"}.fa-hospital-o:before{content:"\\F0F8"}.fa-ambulance:before{content:"\\F0F9"}.fa-medkit:before{content:"\\F0FA"}.fa-fighter-jet:before{content:"\\F0FB"}.fa-beer:before{content:"\\F0FC"}.fa-h-square:before{content:"\\F0FD"}.fa-plus-square:before{content:"\\F0FE"}.fa-angle-double-left:before{content:"\\F100"}.fa-angle-double-right:before{content:"\\F101"}.fa-angle-double-up:before{content:"\\F102"}.fa-angle-double-down:before{content:"\\F103"}.fa-angle-left:before{content:"\\F104"}.fa-angle-right:before{content:"\\F105"}.fa-angle-up:before{content:"\\F106"}.fa-angle-down:before{content:"\\F107"}.fa-desktop:before{content:"\\F108"}.fa-laptop:before{content:"\\F109"}.fa-tablet:before{content:"\\F10A"}.fa-mobile-phone:before,.fa-mobile:before{content:"\\F10B"}.fa-circle-o:before{content:"\\F10C"}.fa-quote-left:before{content:"\\F10D"}.fa-quote-right:before{content:"\\F10E"}.fa-spinner:before{content:"\\F110"}.fa-circle:before{content:"\\F111"}.fa-mail-reply:before,.fa-reply:before{content:"\\F112"}.fa-github-alt:before{content:"\\F113"}.fa-folder-o:before{content:"\\F114"}.fa-folder-open-o:before{content:"\\F115"}.fa-smile-o:before{content:"\\F118"}.fa-frown-o:before{content:"\\F119"}.fa-meh-o:before{content:"\\F11A"}.fa-gamepad:before{content:"\\F11B"}.fa-keyboard-o:before{content:"\\F11C"}.fa-flag-o:before{content:"\\F11D"}.fa-flag-checkered:before{content:"\\F11E"}.fa-terminal:before{content:"\\F120"}.fa-code:before{content:"\\F121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\\F122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\\F123"}.fa-location-arrow:before{content:"\\F124"}.fa-crop:before{content:"\\F125"}.fa-code-fork:before{content:"\\F126"}.fa-chain-broken:before,.fa-unlink:before{content:"\\F127"}.fa-question:before{content:"\\F128"}.fa-info:before{content:"\\F129"}.fa-exclamation:before{content:"\\F12A"}.fa-superscript:before{content:"\\F12B"}.fa-subscript:before{content:"\\F12C"}.fa-eraser:before{content:"\\F12D"}.fa-puzzle-piece:before{content:"\\F12E"}.fa-microphone:before{content:"\\F130"}.fa-microphone-slash:before{content:"\\F131"}.fa-shield:before{content:"\\F132"}.fa-calendar-o:before{content:"\\F133"}.fa-fire-extinguisher:before{content:"\\F134"}.fa-rocket:before{content:"\\F135"}.fa-maxcdn:before{content:"\\F136"}.fa-chevron-circle-left:before{content:"\\F137"}.fa-chevron-circle-right:before{content:"\\F138"}.fa-chevron-circle-up:before{content:"\\F139"}.fa-chevron-circle-down:before{content:"\\F13A"}.fa-html5:before{content:"\\F13B"}.fa-css3:before{content:"\\F13C"}.fa-anchor:before{content:"\\F13D"}.fa-unlock-alt:before{content:"\\F13E"}.fa-bullseye:before{content:"\\F140"}.fa-ellipsis-h:before{content:"\\F141"}.fa-ellipsis-v:before{content:"\\F142"}.fa-rss-square:before{content:"\\F143"}.fa-play-circle:before{content:"\\F144"}.fa-ticket:before{content:"\\F145"}.fa-minus-square:before{content:"\\F146"}.fa-minus-square-o:before{content:"\\F147"}.fa-level-up:before{content:"\\F148"}.fa-level-down:before{content:"\\F149"}.fa-check-square:before{content:"\\F14A"}.fa-pencil-square:before{content:"\\F14B"}.fa-external-link-square:before{content:"\\F14C"}.fa-share-square:before{content:"\\F14D"}.fa-compass:before{content:"\\F14E"}.fa-caret-square-o-down:before,.fa-toggle-down:before{content:"\\F150"}.fa-caret-square-o-up:before,.fa-toggle-up:before{content:"\\F151"}.fa-caret-square-o-right:before,.fa-toggle-right:before{content:"\\F152"}.fa-eur:before,.fa-euro:before{content:"\\F153"}.fa-gbp:before{content:"\\F154"}.fa-dollar:before,.fa-usd:before{content:"\\F155"}.fa-inr:before,.fa-rupee:before{content:"\\F156"}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen:before{content:"\\F157"}.fa-rouble:before,.fa-rub:before,.fa-ruble:before{content:"\\F158"}.fa-krw:before,.fa-won:before{content:"\\F159"}.fa-bitcoin:before,.fa-btc:before{content:"\\F15A"}.fa-file:before{content:"\\F15B"}.fa-file-text:before{content:"\\F15C"}.fa-sort-alpha-asc:before{content:"\\F15D"}.fa-sort-alpha-desc:before{content:"\\F15E"}.fa-sort-amount-asc:before{content:"\\F160"}.fa-sort-amount-desc:before{content:"\\F161"}.fa-sort-numeric-asc:before{content:"\\F162"}.fa-sort-numeric-desc:before{content:"\\F163"}.fa-thumbs-up:before{content:"\\F164"}.fa-thumbs-down:before{content:"\\F165"}.fa-youtube-square:before{content:"\\F166"}.fa-youtube:before{content:"\\F167"}.fa-xing:before{content:"\\F168"}.fa-xing-square:before{content:"\\F169"}.fa-youtube-play:before{content:"\\F16A"}.fa-dropbox:before{content:"\\F16B"}.fa-stack-overflow:before{content:"\\F16C"}.fa-instagram:before{content:"\\F16D"}.fa-flickr:before{content:"\\F16E"}.fa-adn:before{content:"\\F170"}.fa-bitbucket:before{content:"\\F171"}.fa-bitbucket-square:before{content:"\\F172"}.fa-tumblr:before{content:"\\F173"}.fa-tumblr-square:before{content:"\\F174"}.fa-long-arrow-down:before{content:"\\F175"}.fa-long-arrow-up:before{content:"\\F176"}.fa-long-arrow-left:before{content:"\\F177"}.fa-long-arrow-right:before{content:"\\F178"}.fa-apple:before{content:"\\F179"}.fa-windows:before{content:"\\F17A"}.fa-android:before{content:"\\F17B"}.fa-linux:before{content:"\\F17C"}.fa-dribbble:before{content:"\\F17D"}.fa-skype:before{content:"\\F17E"}.fa-foursquare:before{content:"\\F180"}.fa-trello:before{content:"\\F181"}.fa-female:before{content:"\\F182"}.fa-male:before{content:"\\F183"}.fa-gittip:before,.fa-gratipay:before{content:"\\F184"}.fa-sun-o:before{content:"\\F185"}.fa-moon-o:before{content:"\\F186"}.fa-archive:before{content:"\\F187"}.fa-bug:before{content:"\\F188"}.fa-vk:before{content:"\\F189"}.fa-weibo:before{content:"\\F18A"}.fa-renren:before{content:"\\F18B"}.fa-pagelines:before{content:"\\F18C"}.fa-stack-exchange:before{content:"\\F18D"}.fa-arrow-circle-o-right:before{content:"\\F18E"}.fa-arrow-circle-o-left:before{content:"\\F190"}.fa-caret-square-o-left:before,.fa-toggle-left:before{content:"\\F191"}.fa-dot-circle-o:before{content:"\\F192"}.fa-wheelchair:before{content:"\\F193"}.fa-vimeo-square:before{content:"\\F194"}.fa-try:before,.fa-turkish-lira:before{content:"\\F195"}.fa-plus-square-o:before{content:"\\F196"}.fa-space-shuttle:before{content:"\\F197"}.fa-slack:before{content:"\\F198"}.fa-envelope-square:before{content:"\\F199"}.fa-wordpress:before{content:"\\F19A"}.fa-openid:before{content:"\\F19B"}.fa-bank:before,.fa-institution:before,.fa-university:before{content:"\\F19C"}.fa-graduation-cap:before,.fa-mortar-board:before{content:"\\F19D"}.fa-yahoo:before{content:"\\F19E"}.fa-google:before{content:"\\F1A0"}.fa-reddit:before{content:"\\F1A1"}.fa-reddit-square:before{content:"\\F1A2"}.fa-stumbleupon-circle:before{content:"\\F1A3"}.fa-stumbleupon:before{content:"\\F1A4"}.fa-delicious:before{content:"\\F1A5"}.fa-digg:before{content:"\\F1A6"}.fa-pied-piper-pp:before{content:"\\F1A7"}.fa-pied-piper-alt:before{content:"\\F1A8"}.fa-drupal:before{content:"\\F1A9"}.fa-joomla:before{content:"\\F1AA"}.fa-language:before{content:"\\F1AB"}.fa-fax:before{content:"\\F1AC"}.fa-building:before{content:"\\F1AD"}.fa-child:before{content:"\\F1AE"}.fa-paw:before{content:"\\F1B0"}.fa-spoon:before{content:"\\F1B1"}.fa-cube:before{content:"\\F1B2"}.fa-cubes:before{content:"\\F1B3"}.fa-behance:before{content:"\\F1B4"}.fa-behance-square:before{content:"\\F1B5"}.fa-steam:before{content:"\\F1B6"}.fa-steam-square:before{content:"\\F1B7"}.fa-recycle:before{content:"\\F1B8"}.fa-automobile:before,.fa-car:before{content:"\\F1B9"}.fa-cab:before,.fa-taxi:before{content:"\\F1BA"}.fa-tree:before{content:"\\F1BB"}.fa-spotify:before{content:"\\F1BC"}.fa-deviantart:before{content:"\\F1BD"}.fa-soundcloud:before{content:"\\F1BE"}.fa-database:before{content:"\\F1C0"}.fa-file-pdf-o:before{content:"\\F1C1"}.fa-file-word-o:before{content:"\\F1C2"}.fa-file-excel-o:before{content:"\\F1C3"}.fa-file-powerpoint-o:before{content:"\\F1C4"}.fa-file-image-o:before,.fa-file-photo-o:before,.fa-file-picture-o:before{content:"\\F1C5"}.fa-file-archive-o:before,.fa-file-zip-o:before{content:"\\F1C6"}.fa-file-audio-o:before,.fa-file-sound-o:before{content:"\\F1C7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\\F1C8"}.fa-file-code-o:before{content:"\\F1C9"}.fa-vine:before{content:"\\F1CA"}.fa-codepen:before{content:"\\F1CB"}.fa-jsfiddle:before{content:"\\F1CC"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-ring:before,.fa-life-saver:before,.fa-support:before{content:"\\F1CD"}.fa-circle-o-notch:before{content:"\\F1CE"}.fa-ra:before,.fa-rebel:before,.fa-resistance:before{content:"\\F1D0"}.fa-empire:before,.fa-ge:before{content:"\\F1D1"}.fa-git-square:before{content:"\\F1D2"}.fa-git:before{content:"\\F1D3"}.fa-hacker-news:before,.fa-y-combinator-square:before,.fa-yc-square:before{content:"\\F1D4"}.fa-tencent-weibo:before{content:"\\F1D5"}.fa-qq:before{content:"\\F1D6"}.fa-wechat:before,.fa-weixin:before{content:"\\F1D7"}.fa-paper-plane:before,.fa-send:before{content:"\\F1D8"}.fa-paper-plane-o:before,.fa-send-o:before{content:"\\F1D9"}.fa-history:before{content:"\\F1DA"}.fa-circle-thin:before{content:"\\F1DB"}.fa-header:before{content:"\\F1DC"}.fa-paragraph:before{content:"\\F1DD"}.fa-sliders:before{content:"\\F1DE"}.fa-share-alt:before{content:"\\F1E0"}.fa-share-alt-square:before{content:"\\F1E1"}.fa-bomb:before{content:"\\F1E2"}.fa-futbol-o:before,.fa-soccer-ball-o:before{content:"\\F1E3"}.fa-tty:before{content:"\\F1E4"}.fa-binoculars:before{content:"\\F1E5"}.fa-plug:before{content:"\\F1E6"}.fa-slideshare:before{content:"\\F1E7"}.fa-twitch:before{content:"\\F1E8"}.fa-yelp:before{content:"\\F1E9"}.fa-newspaper-o:before{content:"\\F1EA"}.fa-wifi:before{content:"\\F1EB"}.fa-calculator:before{content:"\\F1EC"}.fa-paypal:before{content:"\\F1ED"}.fa-google-wallet:before{content:"\\F1EE"}.fa-cc-visa:before{content:"\\F1F0"}.fa-cc-mastercard:before{content:"\\F1F1"}.fa-cc-discover:before{content:"\\F1F2"}.fa-cc-amex:before{content:"\\F1F3"}.fa-cc-paypal:before{content:"\\F1F4"}.fa-cc-stripe:before{content:"\\F1F5"}.fa-bell-slash:before{content:"\\F1F6"}.fa-bell-slash-o:before{content:"\\F1F7"}.fa-trash:before{content:"\\F1F8"}.fa-copyright:before{content:"\\F1F9"}.fa-at:before{content:"\\F1FA"}.fa-eyedropper:before{content:"\\F1FB"}.fa-paint-brush:before{content:"\\F1FC"}.fa-birthday-cake:before{content:"\\F1FD"}.fa-area-chart:before{content:"\\F1FE"}.fa-pie-chart:before{content:"\\F200"}.fa-line-chart:before{content:"\\F201"}.fa-lastfm:before{content:"\\F202"}.fa-lastfm-square:before{content:"\\F203"}.fa-toggle-off:before{content:"\\F204"}.fa-toggle-on:before{content:"\\F205"}.fa-bicycle:before{content:"\\F206"}.fa-bus:before{content:"\\F207"}.fa-ioxhost:before{content:"\\F208"}.fa-angellist:before{content:"\\F209"}.fa-cc:before{content:"\\F20A"}.fa-ils:before,.fa-shekel:before,.fa-sheqel:before{content:"\\F20B"}.fa-meanpath:before{content:"\\F20C"}.fa-buysellads:before{content:"\\F20D"}.fa-connectdevelop:before{content:"\\F20E"}.fa-dashcube:before{content:"\\F210"}.fa-forumbee:before{content:"\\F211"}.fa-leanpub:before{content:"\\F212"}.fa-sellsy:before{content:"\\F213"}.fa-shirtsinbulk:before{content:"\\F214"}.fa-simplybuilt:before{content:"\\F215"}.fa-skyatlas:before{content:"\\F216"}.fa-cart-plus:before{content:"\\F217"}.fa-cart-arrow-down:before{content:"\\F218"}.fa-diamond:before{content:"\\F219"}.fa-ship:before{content:"\\F21A"}.fa-user-secret:before{content:"\\F21B"}.fa-motorcycle:before{content:"\\F21C"}.fa-street-view:before{content:"\\F21D"}.fa-heartbeat:before{content:"\\F21E"}.fa-venus:before{content:"\\F221"}.fa-mars:before{content:"\\F222"}.fa-mercury:before{content:"\\F223"}.fa-intersex:before,.fa-transgender:before{content:"\\F224"}.fa-transgender-alt:before{content:"\\F225"}.fa-venus-double:before{content:"\\F226"}.fa-mars-double:before{content:"\\F227"}.fa-venus-mars:before{content:"\\F228"}.fa-mars-stroke:before{content:"\\F229"}.fa-mars-stroke-v:before{content:"\\F22A"}.fa-mars-stroke-h:before{content:"\\F22B"}.fa-neuter:before{content:"\\F22C"}.fa-genderless:before{content:"\\F22D"}.fa-facebook-official:before{content:"\\F230"}.fa-pinterest-p:before{content:"\\F231"}.fa-whatsapp:before{content:"\\F232"}.fa-server:before{content:"\\F233"}.fa-user-plus:before{content:"\\F234"}.fa-user-times:before{content:"\\F235"}.fa-bed:before,.fa-hotel:before{content:"\\F236"}.fa-viacoin:before{content:"\\F237"}.fa-train:before{content:"\\F238"}.fa-subway:before{content:"\\F239"}.fa-medium:before{content:"\\F23A"}.fa-y-combinator:before,.fa-yc:before{content:"\\F23B"}.fa-optin-monster:before{content:"\\F23C"}.fa-opencart:before{content:"\\F23D"}.fa-expeditedssl:before{content:"\\F23E"}.fa-battery-4:before,.fa-battery-full:before,.fa-battery:before{content:"\\F240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\\F241"}.fa-battery-2:before,.fa-battery-half:before{content:"\\F242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\\F243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\\F244"}.fa-mouse-pointer:before{content:"\\F245"}.fa-i-cursor:before{content:"\\F246"}.fa-object-group:before{content:"\\F247"}.fa-object-ungroup:before{content:"\\F248"}.fa-sticky-note:before{content:"\\F249"}.fa-sticky-note-o:before{content:"\\F24A"}.fa-cc-jcb:before{content:"\\F24B"}.fa-cc-diners-club:before{content:"\\F24C"}.fa-clone:before{content:"\\F24D"}.fa-balance-scale:before{content:"\\F24E"}.fa-hourglass-o:before{content:"\\F250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\\F251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\\F252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\\F253"}.fa-hourglass:before{content:"\\F254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\\F255"}.fa-hand-paper-o:before,.fa-hand-stop-o:before{content:"\\F256"}.fa-hand-scissors-o:before{content:"\\F257"}.fa-hand-lizard-o:before{content:"\\F258"}.fa-hand-spock-o:before{content:"\\F259"}.fa-hand-pointer-o:before{content:"\\F25A"}.fa-hand-peace-o:before{content:"\\F25B"}.fa-trademark:before{content:"\\F25C"}.fa-registered:before{content:"\\F25D"}.fa-creative-commons:before{content:"\\F25E"}.fa-gg:before{content:"\\F260"}.fa-gg-circle:before{content:"\\F261"}.fa-tripadvisor:before{content:"\\F262"}.fa-odnoklassniki:before{content:"\\F263"}.fa-odnoklassniki-square:before{content:"\\F264"}.fa-get-pocket:before{content:"\\F265"}.fa-wikipedia-w:before{content:"\\F266"}.fa-safari:before{content:"\\F267"}.fa-chrome:before{content:"\\F268"}.fa-firefox:before{content:"\\F269"}.fa-opera:before{content:"\\F26A"}.fa-internet-explorer:before{content:"\\F26B"}.fa-television:before,.fa-tv:before{content:"\\F26C"}.fa-contao:before{content:"\\F26D"}.fa-500px:before{content:"\\F26E"}.fa-amazon:before{content:"\\F270"}.fa-calendar-plus-o:before{content:"\\F271"}.fa-calendar-minus-o:before{content:"\\F272"}.fa-calendar-times-o:before{content:"\\F273"}.fa-calendar-check-o:before{content:"\\F274"}.fa-industry:before{content:"\\F275"}.fa-map-pin:before{content:"\\F276"}.fa-map-signs:before{content:"\\F277"}.fa-map-o:before{content:"\\F278"}.fa-map:before{content:"\\F279"}.fa-commenting:before{content:"\\F27A"}.fa-commenting-o:before{content:"\\F27B"}.fa-houzz:before{content:"\\F27C"}.fa-vimeo:before{content:"\\F27D"}.fa-black-tie:before{content:"\\F27E"}.fa-fonticons:before{content:"\\F280"}.fa-reddit-alien:before{content:"\\F281"}.fa-edge:before{content:"\\F282"}.fa-credit-card-alt:before{content:"\\F283"}.fa-codiepie:before{content:"\\F284"}.fa-modx:before{content:"\\F285"}.fa-fort-awesome:before{content:"\\F286"}.fa-usb:before{content:"\\F287"}.fa-product-hunt:before{content:"\\F288"}.fa-mixcloud:before{content:"\\F289"}.fa-scribd:before{content:"\\F28A"}.fa-pause-circle:before{content:"\\F28B"}.fa-pause-circle-o:before{content:"\\F28C"}.fa-stop-circle:before{content:"\\F28D"}.fa-stop-circle-o:before{content:"\\F28E"}.fa-shopping-bag:before{content:"\\F290"}.fa-shopping-basket:before{content:"\\F291"}.fa-hashtag:before{content:"\\F292"}.fa-bluetooth:before{content:"\\F293"}.fa-bluetooth-b:before{content:"\\F294"}.fa-percent:before{content:"\\F295"}.fa-gitlab:before{content:"\\F296"}.fa-wpbeginner:before{content:"\\F297"}.fa-wpforms:before{content:"\\F298"}.fa-envira:before{content:"\\F299"}.fa-universal-access:before{content:"\\F29A"}.fa-wheelchair-alt:before{content:"\\F29B"}.fa-question-circle-o:before{content:"\\F29C"}.fa-blind:before{content:"\\F29D"}.fa-audio-description:before{content:"\\F29E"}.fa-volume-control-phone:before{content:"\\F2A0"}.fa-braille:before{content:"\\F2A1"}.fa-assistive-listening-systems:before{content:"\\F2A2"}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before{content:"\\F2A3"}.fa-deaf:before,.fa-deafness:before,.fa-hard-of-hearing:before{content:"\\F2A4"}.fa-glide:before{content:"\\F2A5"}.fa-glide-g:before{content:"\\F2A6"}.fa-sign-language:before,.fa-signing:before{content:"\\F2A7"}.fa-low-vision:before{content:"\\F2A8"}.fa-viadeo:before{content:"\\F2A9"}.fa-viadeo-square:before{content:"\\F2AA"}.fa-snapchat:before{content:"\\F2AB"}.fa-snapchat-ghost:before{content:"\\F2AC"}.fa-snapchat-square:before{content:"\\F2AD"}.fa-pied-piper:before{content:"\\F2AE"}.fa-first-order:before{content:"\\F2B0"}.fa-yoast:before{content:"\\F2B1"}.fa-themeisle:before{content:"\\F2B2"}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:"\\F2B3"}.fa-fa:before,.fa-font-awesome:before{content:"\\F2B4"}.fa-handshake-o:before{content:"\\F2B5"}.fa-envelope-open:before{content:"\\F2B6"}.fa-envelope-open-o:before{content:"\\F2B7"}.fa-linode:before{content:"\\F2B8"}.fa-address-book:before{content:"\\F2B9"}.fa-address-book-o:before{content:"\\F2BA"}.fa-address-card:before,.fa-vcard:before{content:"\\F2BB"}.fa-address-card-o:before,.fa-vcard-o:before{content:"\\F2BC"}.fa-user-circle:before{content:"\\F2BD"}.fa-user-circle-o:before{content:"\\F2BE"}.fa-user-o:before{content:"\\F2C0"}.fa-id-badge:before{content:"\\F2C1"}.fa-drivers-license:before,.fa-id-card:before{content:"\\F2C2"}.fa-drivers-license-o:before,.fa-id-card-o:before{content:"\\F2C3"}.fa-quora:before{content:"\\F2C4"}.fa-free-code-camp:before{content:"\\F2C5"}.fa-telegram:before{content:"\\F2C6"}.fa-thermometer-4:before,.fa-thermometer-full:before,.fa-thermometer:before{content:"\\F2C7"}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:"\\F2C8"}.fa-thermometer-2:before,.fa-thermometer-half:before{content:"\\F2C9"}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:"\\F2CA"}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:"\\F2CB"}.fa-shower:before{content:"\\F2CC"}.fa-bath:before,.fa-bathtub:before,.fa-s15:before{content:"\\F2CD"}.fa-podcast:before{content:"\\F2CE"}.fa-window-maximize:before{content:"\\F2D0"}.fa-window-minimize:before{content:"\\F2D1"}.fa-window-restore:before{content:"\\F2D2"}.fa-times-rectangle:before,.fa-window-close:before{content:"\\F2D3"}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:"\\F2D4"}.fa-bandcamp:before{content:"\\F2D5"}.fa-grav:before{content:"\\F2D6"}.fa-etsy:before{content:"\\F2D7"}.fa-imdb:before{content:"\\F2D8"}.fa-ravelry:before{content:"\\F2D9"}.fa-eercast:before{content:"\\F2DA"}.fa-microchip:before{content:"\\F2DB"}.fa-snowflake-o:before{content:"\\F2DC"}.fa-superpowers:before{content:"\\F2DD"}.fa-wpexplorer:before{content:"\\F2DE"}.fa-meetup:before{content:"\\F2E0"}.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}',""]); -},function(A,M,t){M=A.exports=t(138)(),M.push([A.id,'*,:after,:before{box-sizing:border-box}body{font-family:Lato,sans-serif;font-size:15px;line-height:1.42857143;color:#8e8e8e;background-color:#edecec}button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#46a5e0}a,a:focus,a:hover{text-decoration:none}a:focus,a:hover{color:#1f7fba}a:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.img-responsive{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{padding:4px;line-height:1.42857143;background-color:#edecec;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;transition:all .2s ease-in-out;display:inline-block;max-width:100%;height:auto}.img-circle{border-radius:50%}hr{margin-top:21px;margin-bottom:21px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;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}[role=button]{cursor:pointer}.container{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}.row{margin-left:-15px;margin-right:-15px}.col-lg-1,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-10,.col-lg-11,.col-lg-12,.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11,.col-md-12,.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12,.col-xs-1,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-10,.col-xs-11,.col-xs-12{position:relative;min-height:1px;padding-left:15px;padding-right:15px}.col-xs-1,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-10,.col-xs-11,.col-xs-12{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11,.col-md-12{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-10,.col-lg-11,.col-lg-12{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-top:4px solid\\9;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown,.dropup{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;font-size:15px;text-align:left;background-color:#fff;border:1px solid transparent;border-radius:4px;box-shadow:0 6px 12px rgba(0,0,0,.175);background-clip:padding-box}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9.5px 0;overflow:hidden;background-color:rgba(0,0,0,.08)}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#8e8e8e;white-space:nowrap}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{text-decoration:none;color:#333;background-color:rgba(0,0,0,.05)}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{color:#333;text-decoration:none;outline:0;background-color:rgba(0,0,0,.075)}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{color:#e4e4e4}.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{text-decoration:none;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);cursor:not-allowed}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{left:auto;right:0}.dropdown-menu-left{left:0;right:auto}.dropdown-header{display:block;padding:3px 20px;font-size:13px;line-height:1.42857143;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;left:0;right:0;bottom:0;top:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px dashed;border-bottom:4px solid\\9;content:""}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:768px){.navbar-right .dropdown-menu{left:auto;right:0}.navbar-right .dropdown-menu>li>a{text-align:right}.navbar-right .dropdown-menu-left{left:0;right:auto}}.modal,.modal-open{overflow:hidden}.modal{display:none;position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transform:translateY(-25%);transform:translateY(-25%);-webkit-transition:-webkit-transform .3s ease-out;transition:transform .3s ease-out}.modal.in .modal-dialog{-webkit-transform:translate(0);transform:translate(0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;border:1px solid transparent;border-radius:6px;box-shadow:0 3px 9px rgba(0,0,0,.5);background-clip:padding-box;outline:0}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:rgba(0,0,0,.1)}.modal-backdrop.fade{opacity:0;filter:alpha(opacity=0)}.modal-backdrop.in{opacity:.5;filter:alpha(opacity=50)}.modal-header{padding:30px 35px 0;border-bottom:1px solid transparent}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:transparent}.modal-body{position:relative;padding:30px 35px}.modal-footer{padding:30px 35px;text-align:right;border-top:1px solid transparent}.modal-footer .btn+.btn{margin-left:5px;margin-bottom:0}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:400px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-family:Lato,sans-serif;font-style:normal;font-weight:400;letter-spacing:normal;line-break:auto;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;white-space:normal;word-break:normal;word-spacing:normal;word-wrap:normal;font-size:13px;opacity:0;filter:alpha(opacity=0)}.tooltip.in{opacity:.9;filter:alpha(opacity=90)}.tooltip.top{margin-top:-3px;padding:5px 0}.tooltip.right{margin-left:3px;padding:0 5px}.tooltip.bottom{margin-top:3px;padding:5px 0}.tooltip.left{margin-left:-3px;padding:0 5px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{right:5px}.tooltip.top-left .tooltip-arrow,.tooltip.top-right .tooltip-arrow{bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{left:5px}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}@-ms-viewport{width:device-width}.visible-lg,.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block,.visible-md,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-sm,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-xs,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table!important}tr.visible-xs{display:table-row!important}td.visible-xs,th.visible-xs{display:table-cell!important}}@media (max-width:767px){.visible-xs-block{display:block!important}}@media (max-width:767px){.visible-xs-inline{display:inline!important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table!important}tr.visible-sm{display:table-row!important}td.visible-sm,th.visible-sm{display:table-cell!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table!important}tr.visible-md{display:table-row!important}td.visible-md,th.visible-md{display:table-cell!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table!important}tr.visible-lg{display:table-row!important}td.visible-lg,th.visible-lg{display:table-cell!important}}@media (min-width:1200px){.visible-lg-block{display:block!important}}@media (min-width:1200px){.visible-lg-inline{display:inline!important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block!important}}@media (max-width:767px){.hidden-xs{display:none!important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}}@media (min-width:1200px){.hidden-lg{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table!important}tr.visible-print{display:table-row!important}td.visible-print,th.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}}*{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}:active,:focus{outline:0}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body,html{min-height:100%}a{-webkit-transition:color;transition:color;-webkit-transition-duration:.3s;transition-duration:.3s}button{border:0}.animated.infinite{-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite}@-webkit-keyframes fadeIn{0%{opacity:0}to{opacity:1}}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.fadeIn{-webkit-animation-name:fadeIn;animation-name:fadeIn}@-webkit-keyframes fadeInDown{0%{opacity:0;-webkit-transform:translateY(-20px)}to{opacity:1;-webkit-transform:translateY(0)}}@keyframes fadeInDown{0%{opacity:0;transform:translateY(-20px)}to{opacity:1;transform:translateY(0)}}.fadeInDown{-webkit-animation-name:fadeInDown;animation-name:fadeInDown}@-webkit-keyframes fadeInUp{0%{opacity:0;-webkit-transform:translateY(20px)}to{opacity:1;-webkit-transform:translateY(0)}}@keyframes fadeInUp{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.fadeInUp{-webkit-animation-name:fadeInUp;animation-name:fadeInUp}@-webkit-keyframes fadeOut{0%{opacity:1}to{opacity:0}}@keyframes fadeOut{0%{opacity:1}to{opacity:0}}.fadeOut{-webkit-animation-name:fadeOut;animation-name:fadeOut}@-webkit-keyframes fadeOutDown{0%{opacity:1;-webkit-transform:translateY(0)}to{opacity:0;-webkit-transform:translateY(20px)}}@keyframes fadeOutDown{0%{opacity:1;transform:translateY(0)}to{opacity:0;transform:translateY(20px)}}.fadeOutDown{-webkit-animation-name:fadeOutDown;animation-name:fadeOutDown}@-webkit-keyframes fadeOutUp{0%{opacity:1;-webkit-transform:translateY(0)}to{opacity:0;-webkit-transform:translateY(-20px)}}@keyframes fadeOutUp{0%{opacity:1;transform:translateY(0)}to{opacity:0;transform:translateY(-20px)}}.fadeOutUp{-webkit-animation-name:fadeOutUp;animation-name:fadeOutUp}@-webkit-keyframes zoomIn{0%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}50%{opacity:1}}@keyframes zoomIn{0%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}50%{opacity:1}}.text-center{text-align:center!important}.text-left{text-align:left!important}.text-right{text-align:right!important}.clearfix:after,.clearfix:before,.container-fluid:after,.container-fluid:before,.container:after,.container:before,.modal-footer:after,.modal-footer:before,.modal-header:after,.modal-header:before,.row:after,.row:before{content:" ";display:table}.clearfix:after,.container-fluid:after,.container:after,.modal-footer:after,.modal-header:after,.row:after{clear:both}.pull-right{float:right!important}.pull-left{float:left!important}.p-relative{position:relative}.m-0{margin:0!important}.m-t-0{margin-top:0!important}.m-b-0{margin-bottom:0!important}.m-l-0{margin-left:0!important}.m-r-0{margin-right:0!important}.m-5{margin:5px!important}.m-t-5{margin-top:5px!important}.m-b-5{margin-bottom:5px!important}.m-l-5{margin-left:5px!important}.m-r-5{margin-right:5px!important}.m-10{margin:10px!important}.m-t-10{margin-top:10px!important}.m-b-10{margin-bottom:10px!important}.m-l-10{margin-left:10px!important}.m-r-10{margin-right:10px!important}.m-15{margin:15px!important}.m-t-15{margin-top:15px!important}.m-b-15{margin-bottom:15px!important}.m-l-15{margin-left:15px!important}.m-r-15{margin-right:15px!important}.m-20{margin:20px!important}.m-t-20{margin-top:20px!important}.m-b-20{margin-bottom:20px!important}.m-l-20{margin-left:20px!important}.m-r-20{margin-right:20px!important}.m-25{margin:25px!important}.m-t-25{margin-top:25px!important}.m-b-25{margin-bottom:25px!important}.m-l-25{margin-left:25px!important}.m-r-25{margin-right:25px!important}.m-30{margin:30px!important}.m-t-30{margin-top:30px!important}.m-b-30{margin-bottom:30px!important}.m-l-30{margin-left:30px!important}.m-r-30{margin-right:30px!important}.p-0{padding:0!important}.p-t-0{padding-top:0!important}.p-b-0{padding-bottom:0!important}.p-l-0{padding-left:0!important}.p-r-0{padding-right:0!important}.p-5{padding:5px!important}.p-t-5{padding-top:5px!important}.p-b-5{padding-bottom:5px!important}.p-l-5{padding-left:5px!important}.p-r-5{padding-right:5px!important}.p-10{padding:10px!important}.p-t-10{padding-top:10px!important}.p-b-10{padding-bottom:10px!important}.p-l-10{padding-left:10px!important}.p-r-10{padding-right:10px!important}.p-15{padding:15px!important}.p-t-15{padding-top:15px!important}.p-b-15{padding-bottom:15px!important}.p-l-15{padding-left:15px!important}.p-r-15{padding-right:15px!important}.p-20{padding:20px!important}.p-t-20{padding-top:20px!important}.p-b-20{padding-bottom:20px!important}.p-l-20{padding-left:20px!important}.p-r-20{padding-right:20px!important}.p-25{padding:25px!important}.p-t-25{padding-top:25px!important}.p-b-25{padding-bottom:25px!important}.p-l-25{padding-left:25px!important}.p-r-25{padding-right:25px!important}.p-30{padding:30px!important}.p-t-30{padding-top:30px!important}.p-b-30{padding-bottom:30px!important}.p-l-30{padding-left:30px!important}.p-r-30{padding-right:30px!important}@font-face{font-family:Lato;src:url('+t(515)+') format("woff2"),url('+t(514)+') format("woff");font-weight:400;font-style:normal}.form-control{border:0;border-bottom:1px solid #eee;color:#32393f;padding:5px;width:100%;font-size:13px;background-color:transparent}select.form-control{-webkit-appearance:none;-moz-appearance:none;border-radius:0;background:url('+t(518)+') no-repeat bottom 7px right}.input-group{position:relative}.input-group:not(:last-child){margin-bottom:25px}.input-group label:not(.ig-label){font-size:13px;display:block;margin-bottom:10px}.ig-label{position:absolute;text-align:center;bottom:7px;left:0;width:100%;-webkit-transition:all;transition:all;-webkit-transition-duration:.25s;transition-duration:.25s;padding:2px 0 3px;border-radius:2px;font-weight:400}.ig-helpers{z-index:1;width:100%;left:0}.ig-helpers,.ig-helpers:after,.ig-helpers:before{position:absolute;height:2px;bottom:0}.ig-helpers:after,.ig-helpers:before{content:"";width:0;-webkit-transition:all;transition:all;-webkit-transition-duration:.25s;transition-duration:.25s;background-color:#03a9f4}.ig-helpers:before{left:50%}.ig-helpers:after{right:50%}.ig-text{width:100%;height:40px;border:0;background:transparent;text-align:center;position:relative;z-index:1;border-bottom:1px solid #eee;color:#32393f;font-size:13px}.ig-text:focus+.ig-helpers:after,.ig-text:focus+.ig-helpers:before{width:50%}.ig-text:disabled~.ig-label,.ig-text:focus~.ig-label,.ig-text:valid~.ig-label{bottom:35px;font-size:13px;z-index:1}.ig-text:disabled{opacity:.5;filter:alpha(opacity=50)}.ig-dark .ig-text{color:#fff;border-color:hsla(0,0%,100%,.1)}.ig-dark .ig-helpers:after,.ig-dark .ig-helpers:before{background-color:#dfdfdf;height:1px}.ig-left .ig-label,.ig-left .ig-text{text-align:left}.ig-error .ig-label{color:#e23f3f}.ig-error .ig-helpers i:first-child,.ig-error .ig-helpers i:first-child:after,.ig-error .ig-helpers i:first-child:before{background:rgba(226,63,63,.43)}.ig-error .ig-helpers i:last-child,.ig-error .ig-helpers i:last-child:after,.ig-error .ig-helpers i:last-child:before{background:#e23f3f!important}.ig-error:after{content:"\\F05A";font-family:FontAwesome;position:absolute;top:17px;right:9px;font-size:20px;color:#d33d3e}.ig-search:before{font-family:fontAwesome;content:"\\F002";font-size:15px;position:absolute;left:2px;top:8px}.ig-search .ig-text{padding-left:25px}.set-expire{border:1px solid #eee;margin:35px 0 30px}.set-expire-item{padding:9px 5px 3px;position:relative;display:table-cell;width:1%;text-align:center}.set-expire-item:not(:last-child){border-right:1px solid #eee}.set-expire-title{font-size:10px;text-transform:uppercase}.set-expire-value{display:inline-block;overflow:hidden;position:relative;left:-8px}.set-expire-value input{font-size:20px;text-align:center;position:relative;right:-15px;border:0;color:#333;padding:0;height:25px;width:100%;font-weight:400}.set-expire-decrease,.set-expire-increase{position:absolute;width:20px;height:20px;background:url('+t(516)+') no-repeat 50%;background-size:85%;left:50%;margin-left:-10px;opacity:.2;filter:alpha(opacity=20);cursor:pointer}.set-expire-decrease:hover,.set-expire-increase:hover{opacity:.5;filter:alpha(opacity=50)}.set-expire-increase{top:-25px}.set-expire-decrease{bottom:-27px;-webkit-transform:rotate(-180deg);transform:rotate(-180deg)}.btn{border:0;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:2px;text-align:center;-webkit-transition:all;transition:all;-webkit-transition-duration:.3s;transition-duration:.3s}.btn:focus,.btn:hover{opacity:.9;filter:alpha(opacity=90)}.btn-block{display:block;width:100%}.btn-link{color:#545454;background-color:#eee}.btn-link:focus,.btn-link:hover{color:#545454;background-color:#dfdfdf}.btn-danger{color:#fff;background-color:#ff726f}.btn-danger:focus,.btn-danger:hover{color:#fff;background-color:#ff5450}.btn-primary{color:#fff;background-color:#50b2ff}.btn-primary:focus,.btn-primary:hover{color:#fff;background-color:#31a5ff}.btn-success{color:#fff;background-color:#33d46f}.btn-success:focus,.btn-success:hover{color:#fff;background-color:#28c061}.close{right:15px;font-weight:400;opacity:1;font-size:18px;position:absolute;text-align:center;top:16px;z-index:1;padding:0;border:0;background-color:transparent}.close span{width:25px;height:25px;display:block;border-radius:50%;line-height:24px;text-shadow:none}.close:not(.close-alt) span{background-color:hsla(0,0%,100%,.1);color:hsla(0,0%,100%,.8)}.close:not(.close-alt):focus span,.close:not(.close-alt):hover span{background-color:hsla(0,0%,100%,.2);color:#fff}.close-alt span{background-color:#efefef;color:#989898}.close-alt:focus span,.close-alt:hover span{background-color:#e8e8e8;color:#7b7b7b}.hidden{display:none!important}.copy-text input{width:100%;border-radius:1px;border:1px solid #eee;padding:7px 12px;font-size:13px;line-height:100%;cursor:text;-webkit-transition:border-color;transition:border-color;-webkit-transition-duration:.3s;transition-duration:.3s}.copy-text input:hover{border-color:#e1e1e1}.share-availability{margin-bottom:40px}.share-availability:after,.share-availability:before{position:absolute;bottom:-30px;font-size:10px}.share-availability:before{content:"01 Sec";left:0}.share-availability:after{content:"7 days";right:0}.login{height:100vh;min-height:500px;background:#32393f;text-align:center}.login:before{height:calc(100% - 110px);width:1px;content:""}.l-wrap,.login:before{display:inline-block;vertical-align:middle}.l-wrap{width:80%;max-width:500px;margin-top:-50px}.l-wrap.toggled{display:inline-block}.l-wrap .input-group:not(:last-child){margin-bottom:40px}.l-footer{height:110px;padding:0 50px}.lf-logo{float:right}.lf-logo img{width:40px}.lf-server{float:left;color:hsla(0,0%,100%,.4);font-size:20px;font-weight:400;padding-top:40px}@media (max-width:768px){.lf-logo,.lf-server{float:none;display:block;text-align:center;width:100%}.lf-logo{margin-bottom:5px}.lf-server{font-size:15px}}.lw-btn{width:50px;height:50px;border:1px solid #fff;display:inline-block;border-radius:50%;font-size:22px;color:#fff;-webkit-transition:all;transition:all;-webkit-transition-duration:.3s;transition-duration:.3s;opacity:.3;background-color:transparent;line-height:45px;padding:0}.lw-btn:hover{color:#fff;opacity:.8;border-color:#fff}.lw-btn i{display:block;width:100%;padding-left:3px}input:-webkit-autofill{-webkit-box-shadow:0 0 0 50px #32393f inset!important;-webkit-text-fill-color:#fff!important}.fe-header{padding:45px 55px 20px}@media (min-width:992px){.fe-header{position:relative}}@media (max-width:667px){.fe-header{padding:25px 25px 20px}}.fe-header h2{font-size:16px;font-weight:400;margin:0}.fe-header h2>span{margin-bottom:7px;display:inline-block}.fe-header h2>span:not(:first-child):before{content:"/";margin:0 4px;color:#8e8e8e}.fe-header p{margin-top:7px}.feh-usage{margin-top:12px;max-width:285px}@media (max-width:667px){.feh-usage{max-width:100%;font-size:12px}}.feh-usage>ul{margin-top:7px;list-style:none;padding:0}.feh-usage>ul>li{padding-right:0;display:inline-block}.fehu-chart{height:5px;background:#eee;position:relative;border-radius:2px;overflow:hidden}.fehu-chart>div{position:absolute;left:0;height:100%;background:#46a5e0}.feh-actions{list-style:none;padding:0;margin:0;position:absolute;right:35px;top:30px;z-index:11}@media (max-width:991px){.feh-actions{top:7px;right:10px;position:fixed}}.feh-actions>li{display:inline-block;text-align:right;vertical-align:top;line-height:100%}.feh-actions>li>.btn-group>button,.feh-actions>li>a{display:block;height:45px;min-width:45px;text-align:center;border-radius:50%;padding:0;border:0;background:none}@media (min-width:992px){.feh-actions>li>.btn-group>button,.feh-actions>li>a{color:#7b7b7b;font-size:21px;line-height:45px;-webkit-transition:all;transition:all;-webkit-transition-duration:.3s;transition-duration:.3s}.feh-actions>li>.btn-group>button:hover,.feh-actions>li>a:hover{background:rgba(0,0,0,.09)}}@media (max-width:991px){.feh-actions>li>.btn-group>button,.feh-actions>li>a{background:url('+t(237)+') no-repeat 50%}.feh-actions>li>.btn-group>button .fa-reorder,.feh-actions>li>a .fa-reorder{display:none}}@media (max-width:991px){.fe-header-mobile{background-color:#32393f;padding:10px 50px 9px 12px;text-align:center;position:fixed;z-index:10;box-shadow:0 0 10px rgba(0,0,0,.3);left:0;top:0;width:100%}.fe-header-mobile .mh-logo{height:35px;position:relative;top:4px}.feh-trigger{width:41px;height:41px;cursor:pointer;float:left;position:relative;text-align:center}.feh-trigger:after,.feh-trigger:before{content:"";position:absolute;top:0;left:0;width:100%;height:100%;border-radius:50%}.feh-trigger:after{z-index:1}.feh-trigger:before{background:hsla(0,0%,100%,.1);-webkit-transition:all;transition:all;-webkit-transition-duration:.3s;transition-duration:.3s;-webkit-transform:scale(0);transform:scale(0)}.feht-toggled:before{-webkit-transform:scale(1);transform:scale(1)}.feht-toggled .feht-lines{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.feht-toggled .feht-lines>div.top{width:12px;transform:translateX(8px) translateY(1px) rotate(45deg);-webkit-transform:translateX(8px) translateY(1px) rotate(45deg)}.feht-toggled .feht-lines>div.bottom{width:12px;transform:translateX(8px) translateY(-1px) rotate(-45deg);-webkit-transform:translateX(8px) translateY(-1px) rotate(-45deg)}.feht-lines,.feht-lines>div{-webkit-transition:all;transition:all;-webkit-transition-duration:.3s;transition-duration:.3s}.feht-lines{width:18px;height:12px;display:inline-block;margin-top:14px}.feht-lines>div{background-color:#eaeaea;width:18px;height:2px}.feht-lines>div.center{margin:3px 0}}.fe-sidebar{width:300px;background-color:#32393f;position:fixed;height:100%;overflow:hidden;padding:35px}@media (min-width:992px){.fe-sidebar{-webkit-transform:translateZ(0);transform:translateZ(0)}}@media (max-width:991px){.fe-sidebar{padding-top:85px;z-index:9;box-shadow:0 0 10px rgba(0,0,0,.65);-webkit-transition:all;transition:all;-webkit-transition-duration:.3s;transition-duration:.3s;-webkit-transform:translate3d(-315px,0,0);transform:translate3d(-315px,0,0)}.fe-sidebar.toggled{-webkit-transform:translateZ(0);transform:translateZ(0)}}.fe-sidebar a{color:hsla(0,0%,100%,.58)}.fe-sidebar a:hover{color:#fff}.fes-header{margin-bottom:40px}.fes-header h2,.fes-header img{float:left}.fes-header h2{margin:13px 0 0 10px;font-weight:400}.fes-header img{width:32px}.fesl-inner{height:calc(100vh - 260px);overflow:auto;padding:0;margin:0 -35px}.fesl-inner li{position:relative}.fesl-inner li>a{display:block;padding:10px 40px 12px 65px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fesl-inner li>a:before{font-family:FontAwesome;content:"\\F0A0";font-size:17px;position:absolute;top:10px;left:35px;opacity:.8;filter:alpha(opacity=80)}.fesl-inner li>a.fesli-loading:before{content:"";width:20px;height:20px;border-radius:50%;-webkit-animation-name:zoomIn;animation-name:zoomIn;-webkit-animation-duration:.5s;animation-duration:.5s;-webkit-animation-fill-mode:both;animation-fill-mode:both;border:2px solid hsla(0,0%,100%,.1);border-bottom-color:hsla(0,0%,100%,.5);position:absolute;z-index:1;-webkit-animation:zoomIn .25s,spin .7s .25s infinite linear;animation:zoomIn .25s,spin .7s .25s infinite linear;left:32px;top:0;bottom:0;margin:auto}.fesl-inner li.active{background-color:rgba(0,0,0,.2)}.fesl-inner li.active>a{color:#fff}.fesl-inner li:not(.active):hover{background-color:rgba(0,0,0,.1)}.fesl-inner li:not(.active):hover>a{color:#fff}.fesl-inner li:hover .fesli-trigger{opacity:.6;filter:alpha(opacity=60)}.fesl-inner li:hover .fesli-trigger:hover{opacity:1;filter:alpha(opacity=100)}.fesl-inner ul{list-style:none;padding:0;margin:0}.fesl-inner:hover .scrollbar-vertical{opacity:1}.fesli-trigger{filter:alpha(opacity=0);-webkit-transition:all;transition:all;-webkit-transition-duration:.2s;transition-duration:.2s;top:0;right:0;width:40px;cursor:pointer;background:url('+t(237)+') no-repeat 0}.fesli-trigger,.scrollbar-vertical{opacity:0;position:absolute;height:100%}.scrollbar-vertical{right:5px;width:4px;-webkit-transition:opacity;transition:opacity;-webkit-transition-duration:.3s;transition-duration:.3s}.scrollbar-vertical div{border-radius:1px!important;background-color:#6a6a6a!important}.fes-host{position:fixed;left:0;bottom:0;z-index:1;background:#32393f;color:hsla(0,0%,100%,.4);font-size:15px;font-weight:400;width:300px;padding:20px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fes-host>i{margin-right:10px}.fesl-row{padding-right:40px;padding-top:5px;padding-bottom:5px;position:relative}@media (min-width:668px){.fesl-row{display:flex;flex-flow:row nowrap;justify-content:space-between}}.fesl-row:after,.fesl-row:before{content:" ";display:table}.fesl-row:after{clear:both}@media (min-width:668px){header.fesl-row{margin-bottom:20px;border-bottom:1px solid #f0f0f0;padding-left:40px}header.fesl-row .fesl-item,header.fesl-row .fesli-sort{-webkit-transition:all;transition:all;-webkit-transition-duration:.3s;transition-duration:.3s}header.fesl-row .fesl-item{cursor:pointer;color:#8e8e8e;font-weight:500;margin-bottom:-5px}header.fesl-row .fesl-item>.fesli-sort{float:right;margin:4px 0 0;opacity:0;filter:alpha(opacity=0);color:#32393f;font-size:14px}header.fesl-row .fesl-item:hover:not(.fi-actions){background:#f5f5f5;color:#32393f}header.fesl-row .fesl-item:hover:not(.fi-actions)>.fesli-sort{opacity:.5;filter:alpha(opacity=50)}}@media (max-width:667px){header.fesl-row{display:none}}div.fesl-row{padding-left:85px;border-bottom:1px solid transparent;cursor:default}@media (max-width:667px){div.fesl-row{padding-left:70px;padding-right:45px}}div.fesl-row:nth-child(2n){background-color:#fafafa}div.fesl-row:hover{background-color:#fbf7dc}div.fesl-row[data-type]:before{font-family:fontAwesome;width:35px;height:35px;text-align:center;line-height:35px;position:absolute;border-radius:50%;font-size:16px;left:50px;top:9px;color:#fff}@media (max-width:667px){div.fesl-row[data-type]:before{left:20px}}@media (max-width:667px){div.fesl-row[data-type=folder] .fesl-item.fi-name{padding-top:10px;padding-bottom:7px}div.fesl-row[data-type=folder] .fesl-item.fi-modified,div.fesl-row[data-type=folder] .fesl-item.fi-size{display:none}}div.fesl-row[data-type=folder]:before{content:"\\F114";background-color:#a1d6dd}div.fesl-row[data-type=pdf]:before{content:"\\F1C1";background-color:#fa7775}div.fesl-row[data-type=zip]:before{content:"\\F1C6";background-color:#427089}div.fesl-row[data-type=audio]:before{content:"\\F1C7";background-color:#009688}div.fesl-row[data-type=code]:before{content:"\\F1C9";background-color:#997867}div.fesl-row[data-type=excel]:before{content:"\\F1C3";background-color:#64c866}div.fesl-row[data-type=image]:before{content:"\\F1C5";background-color:#f06292}div.fesl-row[data-type=video]:before{content:"\\F1C8";background-color:#f8c363}div.fesl-row[data-type=other]:before{content:"\\F016";background-color:#afafaf}div.fesl-row[data-type=text]:before{content:"\\F0F6";background-color:#8a8a8a}div.fesl-row[data-type=doc]:before{content:"\\F1C2";background-color:#2196f5}div.fesl-row[data-type=presentation]:before{content:"\\F1C4";background-color:#896ea6}div.fesl-row.fesl-loading:before{content:""}div.fesl-row.fesl-loading:after{content:"";width:20px;height:20px;border-radius:50%;-webkit-animation-name:zoomIn;animation-name:zoomIn;-webkit-animation-duration:.5s;animation-duration:.5s;-webkit-animation-fill-mode:both;animation-fill-mode:both;border:2px solid hsla(0,0%,100%,.5);border-bottom-color:#fff;position:absolute;z-index:1;-webkit-animation:zoomIn .25s,spin .7s .25s infinite linear;animation:zoomIn .25s,spin .7s .25s infinite linear;left:57px;top:17px}@media (max-width:667px){div.fesl-row.fesl-loading:after{left:27px}}.fesl-item{display:block}.fesl-item a{color:#818181}@media (min-width:668px){.fesl-item:not(.fi-actions){text-overflow:ellipsis;padding:10px 15px;white-space:nowrap;overflow:hidden}.fesl-item.fi-name{flex:3}.fesl-item.fi-size{width:140px}.fesl-item.fi-modified{width:190px}.fesl-item.fi-actions{width:40px}}@media (max-width:667px){.fesl-item{padding:0}.fesl-item.fi-name{width:100%;margin-bottom:3px}.fesl-item.fi-modified,.fesl-item.fi-size{font-size:12px;color:#b5b5b5;float:left}.fesl-item.fi-modified{max-width:72px;white-space:nowrap;overflow:hidden}.fesl-item.fi-size{margin-right:10px}.fesl-item.fi-actions{position:absolute;top:5px;right:10px}}.fia-toggle{height:36px;width:36px;background:transparent url('+t(517)+') no-repeat 50%;position:relative;top:3px;opacity:.4;filter:alpha(opacity=40)}.fia-toggle:hover{opacity:.7;filter:alpha(opacity=70)}.fi-actions .dropdown-menu{background-color:transparent;box-shadow:none;padding:0;right:38px;left:auto;margin:0;height:100%;text-align:right}.fi-actions .dropdown.open .dropdown-menu .fiad-action{right:0}.fiad-action{height:35px;width:35px;background:#ffc107;display:inline-block;border-radius:50%;text-align:center;line-height:35px;font-weight:400;position:relative;top:4px;margin-left:5px;-webkit-animation-name:fiad-action-anim;animation-name:fiad-action-anim;-webkit-transform-origin:center center;transform-origin:center center;-webkit-backface-visibility:none;backface-visibility:none;box-shadow:0 2px 4px rgba(0,0,0,.1)}.fiad-action:nth-child(2){-webkit-animation-duration:.1s;animation-duration:.1s}.fiad-action:first-child{-webkit-animation-duration:.25s;animation-duration:.25s}.fiad-action>i{font-size:14px;color:#fff}.fiad-action:hover{background-color:#f7b900}@-webkit-keyframes fiad-action-anim{0%{-webkit-transform:scale(0);transform:scale(0);opacity:0;filter:alpha(opacity=0);right:-20px}to{-webkit-transform:scale(1);transform:scale(1);opacity:1;filter:alpha(opacity=100);right:0}}@keyframes fiad-action-anim{0%{-webkit-transform:scale(0);transform:scale(0);opacity:0;filter:alpha(opacity=0);right:-20px}to{-webkit-transform:scale(1);transform:scale(1);opacity:1;filter:alpha(opacity=100);right:0}}.file-explorer{background-color:#fff;position:relative;height:100%}.file-explorer.toggled{height:100vh;overflow:hidden}.fe-body{min-height:100vh;overflow:auto}@media (min-width:992px){.fe-body{padding:0 0 40px 300px}}@media (max-width:991px){.fe-body{padding:75px 0 80px}}.feb-actions{position:fixed;bottom:30px;right:30px}.feb-actions .dropdown-menu{min-width:55px;width:55px;text-align:center;background:transparent;box-shadow:none;margin:0}.feb-actions.open .feba-btn{-webkit-transform:scale(1);transform:scale(1)}.feb-actions.open .feba-btn:first-child{-webkit-animation-name:feba-btn-anim;animation-name:feba-btn-anim;-webkit-animation-duration:.3s;animation-duration:.3s}.feb-actions.open .feba-btn:last-child{-webkit-animation-name:feba-btn-anim;animation-name:feba-btn-anim;-webkit-animation-duration:.1s;animation-duration:.1s}.feb-actions.open .feba-toggle{background:#ff403c}.feb-actions.open .feba-toggle>span{-webkit-transform:rotate(135deg);transform:rotate(135deg)}.feba-toggle{width:55px;height:55px;line-height:55px;border-radius:50%;background:#ff726f;box-shadow:0 2px 3px rgba(0,0,0,.15);display:inline-block;text-align:center;border:0;padding:0}.feba-toggle span{display:inline-block;height:100%;width:100%}.feba-toggle i{color:#fff;font-size:17px;line-height:58px}.feba-toggle,.feba-toggle>span{-webkit-transition:all;transition:all;-webkit-transition-duration:.25s;transition-duration:.25s;-webkit-backface-visibility:hidden;backface-visibility:hidden}.feba-btn{width:40px;margin-top:10px;height:40px;border-radius:50%;text-align:center;display:inline-block;line-height:40px;box-shadow:0 2px 3px rgba(0,0,0,.15);-webkit-transform:scale(0);transform:scale(0);position:relative}.feba-btn,.feba-btn:focus,.feba-btn:hover{color:#fff}.feba-btn label{width:100%;height:100%;position:absolute;left:0;top:0;cursor:pointer}.feba-bucket{background:#ffc155}.feba-upload{background:#ffc107}@-webkit-keyframes feba-btn-anim{0%{-webkit-transform:scale(0);transform:scale(0);opacity:0;filter:alpha(opacity=0)}to{-webkit-transform:scale(1);transform:scale(1);opacity:1;filter:alpha(opacity=100)}}@keyframes feba-btn-anim{0%{-webkit-transform:scale(0);transform:scale(0);opacity:0;filter:alpha(opacity=0)}to{-webkit-transform:scale(1);transform:scale(1);opacity:1;filter:alpha(opacity=100)}}.ie-warning{background-color:#ff5252;width:100%;height:100%;position:fixed;left:0;top:0;text-align:center}.ie-warning:before{width:1px;content:"";height:100%}.ie-warning .iw-inner,.ie-warning:before{display:inline-block;vertical-align:middle}.iw-inner{width:470px;height:300px;background-color:#fff;border-radius:5px;padding:40px;position:relative}.iw-inner ul{list-style:none;padding:0;margin:0;width:230px;margin-left:80px;margin-top:16px}.iw-inner ul>li{float:left}.iw-inner ul>li>a{display:block;padding:10px 15px 7px;font-size:14px;margin:0 1px;border-radius:3px}.iw-inner ul>li>a:hover{background:#eee}.iw-inner ul>li>a img{height:40px;margin-bottom:5px}.iwi-icon{color:#ff5252;font-size:40px;display:block;line-height:100%;margin-bottom:15px}.iwi-skip{position:absolute;left:0;bottom:-35px;width:100%;color:hsla(0,0%,100%,.6);cursor:pointer}.iwi-skip:hover{color:#fff}.dropdown-menu{padding:15px 0;top:0;margin-top:-1px}.dropdown-menu>li>a{padding:8px 20px;font-size:15px}.dropdown-menu>li>a>i{width:20px;position:relative;top:1px}.dropdown-menu-right>li>a{text-align:right}.alert{border:0;position:fixed;max-width:500px;margin:0;box-shadow:0 4px 5px rgba(0,0,0,.1);color:#fff;width:100%;right:20px;border-radius:3px;padding:17px 50px 17px 17px;z-index:10010;-webkit-animation-duration:.8s;animation-duration:.8s;-webkit-animation-fill-mode:both;animation-fill-mode:both}.alert:not(.progress){top:20px}@media (min-width:768px){.alert:not(.progress){left:50%;margin-left:-250px}}.alert.progress{bottom:20px;right:20px}.alert.alert-danger{background:#ff726f}.alert.alert-success{background:#33d46f}.alert.alert-info{background:#50b2ff}@media (max-width:767px){.alert{left:20px;width:calc(100% - 40px);max-width:100%}}.alert .progress{margin:10px 10px 8px 0;height:5px;box-shadow:none;border-radius:1px;background-color:#50b2ff;border-radius:2px;overflow:hidden}.alert .progress-bar{box-shadow:none;background-color:#fff;height:100%}.alert .close{position:absolute;top:15px}@media (min-width:768px){.modal{text-align:center}.modal:before{content:"";height:100%;width:1px}.modal .modal-dialog,.modal:before{display:inline-block;vertical-align:middle}.modal .modal-dialog{text-align:left;margin:10px auto}}.modal-dark .modal-header{color:hsla(0,0%,100%,.4)}.modal-dark .modal-header small{color:hsla(0,0%,100%,.2)}.modal-dark .modal-content{background-color:#32393f}.modal-backdrop{-webkit-animation-name:fadeIn;animation-name:fadeIn;-webkit-animation-fill-mode:both;animation-fill-mode:both}.modal-backdrop,.modal-dialog{-webkit-animation-duration:.2s;animation-duration:.2s}.modal-dialog{-webkit-animation-name:zoomIn;animation-name:zoomIn;-webkit-animation-fill-mode:both;animation-fill-mode:both}.modal-header{color:#333;position:relative}.modal-header small{display:block;text-transform:none;font-size:12px;margin-top:5px;color:#a8a8a8}.modal-content{border-radius:3px;box-shadow:none}.modal-footer{padding:0 30px 30px}.modal-confirm .modal-dialog,.modal-footer{text-align:center}.mc-icon{margin:0 0 10px}.mc-icon>i{font-size:60px}.mci-red{color:#ff8f8f}.mci-amber{color:#ffc107}.mci-green{color:#64e096}.mc-text{color:#333}.mc-sub{color:#bdbdbd;margin-top:5px;font-size:13px}@media (max-width:767px){.modal-about{text-align:center}.modal-about .modal-dialog{max-width:400px;width:90%;margin:20px auto 0}}.ma-inner{display:flex;flex-direction:row;align-items:center;min-height:350px;position:relative}@media (min-width:768px){.ma-inner:before{content:"";width:150px;height:100%;top:0;left:0;position:absolute;border-radius:3px 0 0 3px;background-color:#23282c}}.mai-item:first-child{width:150px;text-align:center}.mai-item:last-child{flex:4;padding:30px}.maii-logo{width:70px;position:relative}.maii-list{list-style:none;padding:0}.maii-list>li{margin-bottom:15px}.maii-list>li div{color:hsla(0,0%,100%,.8);text-transform:uppercase;font-size:14px}.maii-list>li small{font-size:13px;color:hsla(0,0%,100%,.4)}.toggle-password{position:absolute;bottom:30px;right:35px;width:30px;height:30px;border:1px solid #eee;border-radius:0;text-align:center;cursor:pointer;z-index:10;background-color:#fff;padding-top:5px}.toggle-password.toggled{background:#eee}.pm-body{padding-bottom:30px}.pmb-header{margin-bottom:35px}.pmb-list{display:flex;flex-flow:row nowrap;align-items:center;justify-content:center;padding:10px 35px}.pmb-list:nth-child(2n){background-color:#f7f7f7}.pmb-list .form-control{padding-left:0;padding-right:0}header.pmb-list{margin:20px 0 10px}.pmbl-item{display:block;font-size:13px}.pmbl-item:first-child{flex:2}.pmbl-item:nth-child(2){margin:0 25px;width:150px}.pmbl-item:nth-child(3){width:70px}div.pmb-list select{border:0}div.pmb-list .pml-item:not(:last-child){padding:0 5px}.modal-create-bucket .modal-dialog{position:fixed;right:25px;bottom:95px;margin:0;height:110px}.modal-create-bucket .modal-content{width:100%;height:100%}',""]); -},function(A,M,t){function I(A){return null===A||void 0===A}function g(A){return!(!A||"object"!=typeof A||"number"!=typeof A.length)&&("function"==typeof A.copy&&"function"==typeof A.slice&&!(A.length>0&&"number"!=typeof A[0]))}function e(A,M,t){var e,o;if(I(A)||I(M))return!1;if(A.prototype!==M.prototype)return!1;if(E(A))return!!E(M)&&(A=i.call(A),M=i.call(M),N(A,M,t));if(g(A)){if(!g(M))return!1;if(A.length!==M.length)return!1;for(e=0;e=0;e--)if(n[e]!=C[e])return!1;for(e=n.length-1;e>=0;e--)if(o=n[e],!N(A[o],M[o],t))return!1;return typeof A==typeof M}var i=Array.prototype.slice,T=t(280),E=t(279),N=A.exports=function(A,M,t){return t||(t={}),A===M||(A instanceof Date&&M instanceof Date?A.getTime()===M.getTime():!A||!M||"object"!=typeof A&&"object"!=typeof M?t.strict?A===M:A==M:e(A,M,t))}},function(A,M){function t(A){return"[object Arguments]"==Object.prototype.toString.call(A)}function I(A){return A&&"object"==typeof A&&"number"==typeof A.length&&Object.prototype.hasOwnProperty.call(A,"callee")&&!Object.prototype.propertyIsEnumerable.call(A,"callee")||!1}var g="[object Arguments]"==function(){return Object.prototype.toString.call(arguments)}();M=A.exports=g?t:I,M.supported=t,M.unsupported=I},function(A,M){function t(A){var M=[];for(var t in A)M.push(t);return M}M=A.exports="function"==typeof Object.keys?Object.keys:t,M.shim=t},function(A,M,t){"use strict";var I=t(141);A.exports=function(A,M){A.classList?A.classList.add(M):I(A)||(A.className=A.className+" "+M)}},function(A,M,t){"use strict";A.exports={addClass:t(281),removeClass:t(283),hasClass:t(141)}},function(A,M){"use strict";A.exports=function(A,M){A.classList?A.classList.remove(M):A.className=A.className.replace(new RegExp("(^|\\s)"+M+"(?:\\s|$)","g"),"$1").replace(/\s+/g," ").replace(/^\s*|\s*$/g,"")}},function(A,M,t){"use strict";var I=t(40),g=t(288);A.exports=function(A,M){return function(t){var e=t.currentTarget,i=t.target,T=g(e,A);T.some(function(A){return I(A,i)})&&M.call(this,t)}}},function(A,M,t){"use strict";var I=t(81),g=t(142),e=t(284);A.exports={on:I,off:g,filter:e}},function(A,M,t){"use strict";function I(A){return A.nodeName&&A.nodeName.toLowerCase()}function g(A){for(var M=(0,T.default)(A),t=A&&A.offsetParent;t&&"html"!==I(A)&&"static"===(0,N.default)(t,"position");)t=t.offsetParent;return t||M.documentElement}var e=t(58);M.__esModule=!0,M.default=g;var i=t(39),T=e.interopRequireDefault(i),E=t(82),N=e.interopRequireDefault(E);A.exports=M.default},function(A,M,t){"use strict";function I(A){return A.nodeName&&A.nodeName.toLowerCase()}function g(A,M){var t,g={top:0,left:0};return"fixed"===(0,a.default)(A,"position")?t=A.getBoundingClientRect():(M=M||(0,N.default)(A),t=(0,T.default)(A),"html"!==I(M)&&(g=(0,T.default)(M)),g.top+=parseInt((0,a.default)(M,"borderTopWidth"),10)-(0,n.default)(M)||0,g.left+=parseInt((0,a.default)(M,"borderLeftWidth"),10)-(0,c.default)(M)||0),e._extends({},t,{top:t.top-g.top-(parseInt((0,a.default)(A,"marginTop"),10)||0),left:t.left-g.left-(parseInt((0,a.default)(A,"marginLeft"),10)||0)})}var e=t(58);M.__esModule=!0,M.default=g;var i=t(143),T=e.interopRequireDefault(i),E=t(286),N=e.interopRequireDefault(E),o=t(144),n=e.interopRequireDefault(o),C=t(289),c=e.interopRequireDefault(C),D=t(82),a=e.interopRequireDefault(D);A.exports=M.default},function(A,M){"use strict";var t=/^[\w-]*$/,I=Function.prototype.bind.call(Function.prototype.call,[].slice);A.exports=function(A,M){var g,e="#"===M[0],i="."===M[0],T=e||i?M.slice(1):M,E=t.test(T);return E?e?(A=A.getElementById?A:document,(g=A.getElementById(T))?[g]:[]):I(A.getElementsByClassName&&i?A.getElementsByClassName(T):A.getElementsByTagName(M)):I(A.querySelectorAll(M))}},function(A,M,t){"use strict";var I=t(57);A.exports=function(A,M){var t=I(A);return void 0===M?t?"pageXOffset"in t?t.pageXOffset:t.document.documentElement.scrollLeft:A.scrollLeft:void(t?t.scrollTo(M,"pageYOffset"in t?t.pageYOffset:t.document.documentElement.scrollTop):A.scrollLeft=M)}},function(A,M,t){"use strict";var I=t(58),g=t(145),e=I.interopRequireDefault(g),i=/^(top|right|bottom|left)$/,T=/^([+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|))(?!px)[a-z%]+$/i;A.exports=function(A){if(!A)throw new TypeError("No Element passed to ` + "`" + `getComputedStyle()` + "`" + `");var M=A.ownerDocument;return"defaultView"in M?M.defaultView.opener?A.ownerDocument.defaultView.getComputedStyle(A,null):window.getComputedStyle(A,null):{getPropertyValue:function(M){var t=A.style;M=(0,e.default)(M),"float"==M&&(M="styleFloat");var I=A.currentStyle[M]||null;if(null==I&&t&&t[M]&&(I=t[M]),T.test(I)&&!i.test(M)){var g=t.left,E=A.runtimeStyle,N=E&&E.left;N&&(E.left=A.currentStyle.left),t.left="fontSize"===M?"1em":I,I=t.pixelLeft+"px",t.left=g,N&&(E.left=N)}return I}}}},function(A,M){"use strict";A.exports=function(A,M){return"removeProperty"in A.style?A.style.removeProperty(M):A.style.removeAttribute(M)}},function(A,M,t){"use strict";function I(){var A,M="",t={O:"otransitionend",Moz:"transitionend",Webkit:"webkitTransitionEnd",ms:"MSTransitionEnd"},I=document.createElement("div");for(var g in t)if(N.call(t,g)&&void 0!==I.style[g+"TransitionProperty"]){M="-"+g.toLowerCase()+"-",A=t[g];break}return A||void 0===I.style.transitionProperty||(A="transitionend"),{end:A,prefix:M}}var g,e,i,T,E=t(32),N=Object.prototype.hasOwnProperty,o="transform",n={};E&&(n=I(),o=n.prefix+o,i=n.prefix+"transition-property",e=n.prefix+"transition-duration",T=n.prefix+"transition-delay",g=n.prefix+"transition-timing-function"),A.exports={transform:o,end:n.end,property:i,timing:g,delay:T,duration:e}},function(A,M){"use strict";var t=/-(.)/g;A.exports=function(A){return A.replace(t,function(A,M){return M.toUpperCase()})}},function(A,M){"use strict";var t=/([A-Z])/g;A.exports=function(A){return A.replace(t,"-$1").toLowerCase()}},function(A,M,t){"use strict";var I=t(294),g=/^ms-/;A.exports=function(A){return I(A).replace(g,"-ms-")}},function(A,M){"use strict";function t(A){return A.replace(I,function(A,M){return M.toUpperCase()})}var I=/-(.)/g;A.exports=t},function(A,M,t){"use strict";function I(A){return g(A.replace(e,"ms-"))}var g=t(296),e=/^-ms-/;A.exports=I},function(A,M,t){"use strict";function I(A){return!!A&&("object"==typeof A||"function"==typeof A)&&"length"in A&&!("setInterval"in A)&&"number"!=typeof A.nodeType&&(Array.isArray(A)||"callee"in A||"item"in A)}function g(A){return I(A)?Array.isArray(A)?A.slice():e(A):[A]}var e=t(307);A.exports=g},function(A,M,t){"use strict";function I(A){var M=A.match(o);return M&&M[1].toLowerCase()}function g(A,M){var t=N;N?void 0:E(!1);var g=I(A),e=g&&T(g);if(e){t.innerHTML=e[1]+A+e[2];for(var o=e[0];o--;)t=t.lastChild}else t.innerHTML=A;var n=t.getElementsByTagName("script");n.length&&(M?void 0:E(!1),i(n).forEach(M));for(var C=i(t.childNodes);t.lastChild;)t.removeChild(t.lastChild);return C}var e=t(10),i=t(298),T=t(151),E=t(2),N=e.canUseDOM?document.createElement("div"):null,o=/^\s*<(\w+)/;A.exports=g},function(A,M){"use strict";function t(A){return A===window?{x:window.pageXOffset||document.documentElement.scrollLeft,y:window.pageYOffset||document.documentElement.scrollTop}:{x:A.scrollLeft,y:A.scrollTop}}A.exports=t},function(A,M){"use strict";function t(A){return A.replace(I,"-$1").toLowerCase()}var I=/([A-Z])/g;A.exports=t},function(A,M,t){"use strict";function I(A){return g(A).replace(e,"-ms-")}var g=t(301),e=/^ms-/;A.exports=I},function(A,M){"use strict";function t(A){return!(!A||!("function"==typeof Node?A instanceof Node:"object"==typeof A&&"number"==typeof A.nodeType&&"string"==typeof A.nodeName))}A.exports=t},function(A,M,t){"use strict";function I(A){return g(A)&&3==A.nodeType}var g=t(303);A.exports=I},function(A,M){"use strict";function t(A,M,t){if(!A)return null;var g={};for(var e in A)I.call(A,e)&&(g[e]=M.call(t,A[e],e,A));return g}var I=Object.prototype.hasOwnProperty;A.exports=t},function(A,M){"use strict";function t(A){var M={};return function(t){return M.hasOwnProperty(t)||(M[t]=A.call(this,t)),M[t]}}A.exports=t},function(A,M,t){"use strict";function I(A){var M=A.length;if(Array.isArray(A)||"object"!=typeof A&&"function"!=typeof A?g(!1):void 0,"number"!=typeof M?g(!1):void 0,0===M||M-1 in A?void 0:g(!1),A.hasOwnProperty)try{return Array.prototype.slice.call(A)}catch(A){}for(var t=Array(M),I=0;Ili{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.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 infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-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{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:"\\F000"}.fa-music:before{content:"\\F001"}.fa-search:before{content:"\\F002"}.fa-envelope-o:before{content:"\\F003"}.fa-heart:before{content:"\\F004"}.fa-star:before{content:"\\F005"}.fa-star-o:before{content:"\\F006"}.fa-user:before{content:"\\F007"}.fa-film:before{content:"\\F008"}.fa-th-large:before{content:"\\F009"}.fa-th:before{content:"\\F00A"}.fa-th-list:before{content:"\\F00B"}.fa-check:before{content:"\\F00C"}.fa-close:before,.fa-remove:before,.fa-times:before{content:"\\F00D"}.fa-search-plus:before{content:"\\F00E"}.fa-search-minus:before{content:"\\F010"}.fa-power-off:before{content:"\\F011"}.fa-signal:before{content:"\\F012"}.fa-cog:before,.fa-gear:before{content:"\\F013"}.fa-trash-o:before{content:"\\F014"}.fa-home:before{content:"\\F015"}.fa-file-o:before{content:"\\F016"}.fa-clock-o:before{content:"\\F017"}.fa-road:before{content:"\\F018"}.fa-download:before{content:"\\F019"}.fa-arrow-circle-o-down:before{content:"\\F01A"}.fa-arrow-circle-o-up:before{content:"\\F01B"}.fa-inbox:before{content:"\\F01C"}.fa-play-circle-o:before{content:"\\F01D"}.fa-repeat:before,.fa-rotate-right:before{content:"\\F01E"}.fa-refresh:before{content:"\\F021"}.fa-list-alt:before{content:"\\F022"}.fa-lock:before{content:"\\F023"}.fa-flag:before{content:"\\F024"}.fa-headphones:before{content:"\\F025"}.fa-volume-off:before{content:"\\F026"}.fa-volume-down:before{content:"\\F027"}.fa-volume-up:before{content:"\\F028"}.fa-qrcode:before{content:"\\F029"}.fa-barcode:before{content:"\\F02A"}.fa-tag:before{content:"\\F02B"}.fa-tags:before{content:"\\F02C"}.fa-book:before{content:"\\F02D"}.fa-bookmark:before{content:"\\F02E"}.fa-print:before{content:"\\F02F"}.fa-camera:before{content:"\\F030"}.fa-font:before{content:"\\F031"}.fa-bold:before{content:"\\F032"}.fa-italic:before{content:"\\F033"}.fa-text-height:before{content:"\\F034"}.fa-text-width:before{content:"\\F035"}.fa-align-left:before{content:"\\F036"}.fa-align-center:before{content:"\\F037"}.fa-align-right:before{content:"\\F038"}.fa-align-justify:before{content:"\\F039"}.fa-list:before{content:"\\F03A"}.fa-dedent:before,.fa-outdent:before{content:"\\F03B"}.fa-indent:before{content:"\\F03C"}.fa-video-camera:before{content:"\\F03D"}.fa-image:before,.fa-photo:before,.fa-picture-o:before{content:"\\F03E"}.fa-pencil:before{content:"\\F040"}.fa-map-marker:before{content:"\\F041"}.fa-adjust:before{content:"\\F042"}.fa-tint:before{content:"\\F043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\\F044"}.fa-share-square-o:before{content:"\\F045"}.fa-check-square-o:before{content:"\\F046"}.fa-arrows:before{content:"\\F047"}.fa-step-backward:before{content:"\\F048"}.fa-fast-backward:before{content:"\\F049"}.fa-backward:before{content:"\\F04A"}.fa-play:before{content:"\\F04B"}.fa-pause:before{content:"\\F04C"}.fa-stop:before{content:"\\F04D"}.fa-forward:before{content:"\\F04E"}.fa-fast-forward:before{content:"\\F050"}.fa-step-forward:before{content:"\\F051"}.fa-eject:before{content:"\\F052"}.fa-chevron-left:before{content:"\\F053"}.fa-chevron-right:before{content:"\\F054"}.fa-plus-circle:before{content:"\\F055"}.fa-minus-circle:before{content:"\\F056"}.fa-times-circle:before{content:"\\F057"}.fa-check-circle:before{content:"\\F058"}.fa-question-circle:before{content:"\\F059"}.fa-info-circle:before{content:"\\F05A"}.fa-crosshairs:before{content:"\\F05B"}.fa-times-circle-o:before{content:"\\F05C"}.fa-check-circle-o:before{content:"\\F05D"}.fa-ban:before{content:"\\F05E"}.fa-arrow-left:before{content:"\\F060"}.fa-arrow-right:before{content:"\\F061"}.fa-arrow-up:before{content:"\\F062"}.fa-arrow-down:before{content:"\\F063"}.fa-mail-forward:before,.fa-share:before{content:"\\F064"}.fa-expand:before{content:"\\F065"}.fa-compress:before{content:"\\F066"}.fa-plus:before{content:"\\F067"}.fa-minus:before{content:"\\F068"}.fa-asterisk:before{content:"\\F069"}.fa-exclamation-circle:before{content:"\\F06A"}.fa-gift:before{content:"\\F06B"}.fa-leaf:before{content:"\\F06C"}.fa-fire:before{content:"\\F06D"}.fa-eye:before{content:"\\F06E"}.fa-eye-slash:before{content:"\\F070"}.fa-exclamation-triangle:before,.fa-warning:before{content:"\\F071"}.fa-plane:before{content:"\\F072"}.fa-calendar:before{content:"\\F073"}.fa-random:before{content:"\\F074"}.fa-comment:before{content:"\\F075"}.fa-magnet:before{content:"\\F076"}.fa-chevron-up:before{content:"\\F077"}.fa-chevron-down:before{content:"\\F078"}.fa-retweet:before{content:"\\F079"}.fa-shopping-cart:before{content:"\\F07A"}.fa-folder:before{content:"\\F07B"}.fa-folder-open:before{content:"\\F07C"}.fa-arrows-v:before{content:"\\F07D"}.fa-arrows-h:before{content:"\\F07E"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\\F080"}.fa-twitter-square:before{content:"\\F081"}.fa-facebook-square:before{content:"\\F082"}.fa-camera-retro:before{content:"\\F083"}.fa-key:before{content:"\\F084"}.fa-cogs:before,.fa-gears:before{content:"\\F085"}.fa-comments:before{content:"\\F086"}.fa-thumbs-o-up:before{content:"\\F087"}.fa-thumbs-o-down:before{content:"\\F088"}.fa-star-half:before{content:"\\F089"}.fa-heart-o:before{content:"\\F08A"}.fa-sign-out:before{content:"\\F08B"}.fa-linkedin-square:before{content:"\\F08C"}.fa-thumb-tack:before{content:"\\F08D"}.fa-external-link:before{content:"\\F08E"}.fa-sign-in:before{content:"\\F090"}.fa-trophy:before{content:"\\F091"}.fa-github-square:before{content:"\\F092"}.fa-upload:before{content:"\\F093"}.fa-lemon-o:before{content:"\\F094"}.fa-phone:before{content:"\\F095"}.fa-square-o:before{content:"\\F096"}.fa-bookmark-o:before{content:"\\F097"}.fa-phone-square:before{content:"\\F098"}.fa-twitter:before{content:"\\F099"}.fa-facebook-f:before,.fa-facebook:before{content:"\\F09A"}.fa-github:before{content:"\\F09B"}.fa-unlock:before{content:"\\F09C"}.fa-credit-card:before{content:"\\F09D"}.fa-feed:before,.fa-rss:before{content:"\\F09E"}.fa-hdd-o:before{content:"\\F0A0"}.fa-bullhorn:before{content:"\\F0A1"}.fa-bell:before{content:"\\F0F3"}.fa-certificate:before{content:"\\F0A3"}.fa-hand-o-right:before{content:"\\F0A4"}.fa-hand-o-left:before{content:"\\F0A5"}.fa-hand-o-up:before{content:"\\F0A6"}.fa-hand-o-down:before{content:"\\F0A7"}.fa-arrow-circle-left:before{content:"\\F0A8"}.fa-arrow-circle-right:before{content:"\\F0A9"}.fa-arrow-circle-up:before{content:"\\F0AA"}.fa-arrow-circle-down:before{content:"\\F0AB"}.fa-globe:before{content:"\\F0AC"}.fa-wrench:before{content:"\\F0AD"}.fa-tasks:before{content:"\\F0AE"}.fa-filter:before{content:"\\F0B0"}.fa-briefcase:before{content:"\\F0B1"}.fa-arrows-alt:before{content:"\\F0B2"}.fa-group:before,.fa-users:before{content:"\\F0C0"}.fa-chain:before,.fa-link:before{content:"\\F0C1"}.fa-cloud:before{content:"\\F0C2"}.fa-flask:before{content:"\\F0C3"}.fa-cut:before,.fa-scissors:before{content:"\\F0C4"}.fa-copy:before,.fa-files-o:before{content:"\\F0C5"}.fa-paperclip:before{content:"\\F0C6"}.fa-floppy-o:before,.fa-save:before{content:"\\F0C7"}.fa-square:before{content:"\\F0C8"}.fa-bars:before,.fa-navicon:before,.fa-reorder:before{content:"\\F0C9"}.fa-list-ul:before{content:"\\F0CA"}.fa-list-ol:before{content:"\\F0CB"}.fa-strikethrough:before{content:"\\F0CC"}.fa-underline:before{content:"\\F0CD"}.fa-table:before{content:"\\F0CE"}.fa-magic:before{content:"\\F0D0"}.fa-truck:before{content:"\\F0D1"}.fa-pinterest:before{content:"\\F0D2"}.fa-pinterest-square:before{content:"\\F0D3"}.fa-google-plus-square:before{content:"\\F0D4"}.fa-google-plus:before{content:"\\F0D5"}.fa-money:before{content:"\\F0D6"}.fa-caret-down:before{content:"\\F0D7"}.fa-caret-up:before{content:"\\F0D8"}.fa-caret-left:before{content:"\\F0D9"}.fa-caret-right:before{content:"\\F0DA"}.fa-columns:before{content:"\\F0DB"}.fa-sort:before,.fa-unsorted:before{content:"\\F0DC"}.fa-sort-desc:before,.fa-sort-down:before{content:"\\F0DD"}.fa-sort-asc:before,.fa-sort-up:before{content:"\\F0DE"}.fa-envelope:before{content:"\\F0E0"}.fa-linkedin:before{content:"\\F0E1"}.fa-rotate-left:before,.fa-undo:before{content:"\\F0E2"}.fa-gavel:before,.fa-legal:before{content:"\\F0E3"}.fa-dashboard:before,.fa-tachometer:before{content:"\\F0E4"}.fa-comment-o:before{content:"\\F0E5"}.fa-comments-o:before{content:"\\F0E6"}.fa-bolt:before,.fa-flash:before{content:"\\F0E7"}.fa-sitemap:before{content:"\\F0E8"}.fa-umbrella:before{content:"\\F0E9"}.fa-clipboard:before,.fa-paste:before{content:"\\F0EA"}.fa-lightbulb-o:before{content:"\\F0EB"}.fa-exchange:before{content:"\\F0EC"}.fa-cloud-download:before{content:"\\F0ED"}.fa-cloud-upload:before{content:"\\F0EE"}.fa-user-md:before{content:"\\F0F0"}.fa-stethoscope:before{content:"\\F0F1"}.fa-suitcase:before{content:"\\F0F2"}.fa-bell-o:before{content:"\\F0A2"}.fa-coffee:before{content:"\\F0F4"}.fa-cutlery:before{content:"\\F0F5"}.fa-file-text-o:before{content:"\\F0F6"}.fa-building-o:before{content:"\\F0F7"}.fa-hospital-o:before{content:"\\F0F8"}.fa-ambulance:before{content:"\\F0F9"}.fa-medkit:before{content:"\\F0FA"}.fa-fighter-jet:before{content:"\\F0FB"}.fa-beer:before{content:"\\F0FC"}.fa-h-square:before{content:"\\F0FD"}.fa-plus-square:before{content:"\\F0FE"}.fa-angle-double-left:before{content:"\\F100"}.fa-angle-double-right:before{content:"\\F101"}.fa-angle-double-up:before{content:"\\F102"}.fa-angle-double-down:before{content:"\\F103"}.fa-angle-left:before{content:"\\F104"}.fa-angle-right:before{content:"\\F105"}.fa-angle-up:before{content:"\\F106"}.fa-angle-down:before{content:"\\F107"}.fa-desktop:before{content:"\\F108"}.fa-laptop:before{content:"\\F109"}.fa-tablet:before{content:"\\F10A"}.fa-mobile-phone:before,.fa-mobile:before{content:"\\F10B"}.fa-circle-o:before{content:"\\F10C"}.fa-quote-left:before{content:"\\F10D"}.fa-quote-right:before{content:"\\F10E"}.fa-spinner:before{content:"\\F110"}.fa-circle:before{content:"\\F111"}.fa-mail-reply:before,.fa-reply:before{content:"\\F112"}.fa-github-alt:before{content:"\\F113"}.fa-folder-o:before{content:"\\F114"}.fa-folder-open-o:before{content:"\\F115"}.fa-smile-o:before{content:"\\F118"}.fa-frown-o:before{content:"\\F119"}.fa-meh-o:before{content:"\\F11A"}.fa-gamepad:before{content:"\\F11B"}.fa-keyboard-o:before{content:"\\F11C"}.fa-flag-o:before{content:"\\F11D"}.fa-flag-checkered:before{content:"\\F11E"}.fa-terminal:before{content:"\\F120"}.fa-code:before{content:"\\F121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\\F122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\\F123"}.fa-location-arrow:before{content:"\\F124"}.fa-crop:before{content:"\\F125"}.fa-code-fork:before{content:"\\F126"}.fa-chain-broken:before,.fa-unlink:before{content:"\\F127"}.fa-question:before{content:"\\F128"}.fa-info:before{content:"\\F129"}.fa-exclamation:before{content:"\\F12A"}.fa-superscript:before{content:"\\F12B"}.fa-subscript:before{content:"\\F12C"}.fa-eraser:before{content:"\\F12D"}.fa-puzzle-piece:before{content:"\\F12E"}.fa-microphone:before{content:"\\F130"}.fa-microphone-slash:before{content:"\\F131"}.fa-shield:before{content:"\\F132"}.fa-calendar-o:before{content:"\\F133"}.fa-fire-extinguisher:before{content:"\\F134"}.fa-rocket:before{content:"\\F135"}.fa-maxcdn:before{content:"\\F136"}.fa-chevron-circle-left:before{content:"\\F137"}.fa-chevron-circle-right:before{content:"\\F138"}.fa-chevron-circle-up:before{content:"\\F139"}.fa-chevron-circle-down:before{content:"\\F13A"}.fa-html5:before{content:"\\F13B"}.fa-css3:before{content:"\\F13C"}.fa-anchor:before{content:"\\F13D"}.fa-unlock-alt:before{content:"\\F13E"}.fa-bullseye:before{content:"\\F140"}.fa-ellipsis-h:before{content:"\\F141"}.fa-ellipsis-v:before{content:"\\F142"}.fa-rss-square:before{content:"\\F143"}.fa-play-circle:before{content:"\\F144"}.fa-ticket:before{content:"\\F145"}.fa-minus-square:before{content:"\\F146"}.fa-minus-square-o:before{content:"\\F147"}.fa-level-up:before{content:"\\F148"}.fa-level-down:before{content:"\\F149"}.fa-check-square:before{content:"\\F14A"}.fa-pencil-square:before{content:"\\F14B"}.fa-external-link-square:before{content:"\\F14C"}.fa-share-square:before{content:"\\F14D"}.fa-compass:before{content:"\\F14E"}.fa-caret-square-o-down:before,.fa-toggle-down:before{content:"\\F150"}.fa-caret-square-o-up:before,.fa-toggle-up:before{content:"\\F151"}.fa-caret-square-o-right:before,.fa-toggle-right:before{content:"\\F152"}.fa-eur:before,.fa-euro:before{content:"\\F153"}.fa-gbp:before{content:"\\F154"}.fa-dollar:before,.fa-usd:before{content:"\\F155"}.fa-inr:before,.fa-rupee:before{content:"\\F156"}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen:before{content:"\\F157"}.fa-rouble:before,.fa-rub:before,.fa-ruble:before{content:"\\F158"}.fa-krw:before,.fa-won:before{content:"\\F159"}.fa-bitcoin:before,.fa-btc:before{content:"\\F15A"}.fa-file:before{content:"\\F15B"}.fa-file-text:before{content:"\\F15C"}.fa-sort-alpha-asc:before{content:"\\F15D"}.fa-sort-alpha-desc:before{content:"\\F15E"}.fa-sort-amount-asc:before{content:"\\F160"}.fa-sort-amount-desc:before{content:"\\F161"}.fa-sort-numeric-asc:before{content:"\\F162"}.fa-sort-numeric-desc:before{content:"\\F163"}.fa-thumbs-up:before{content:"\\F164"}.fa-thumbs-down:before{content:"\\F165"}.fa-youtube-square:before{content:"\\F166"}.fa-youtube:before{content:"\\F167"}.fa-xing:before{content:"\\F168"}.fa-xing-square:before{content:"\\F169"}.fa-youtube-play:before{content:"\\F16A"}.fa-dropbox:before{content:"\\F16B"}.fa-stack-overflow:before{content:"\\F16C"}.fa-instagram:before{content:"\\F16D"}.fa-flickr:before{content:"\\F16E"}.fa-adn:before{content:"\\F170"}.fa-bitbucket:before{content:"\\F171"}.fa-bitbucket-square:before{content:"\\F172"}.fa-tumblr:before{content:"\\F173"}.fa-tumblr-square:before{content:"\\F174"}.fa-long-arrow-down:before{content:"\\F175"}.fa-long-arrow-up:before{content:"\\F176"}.fa-long-arrow-left:before{content:"\\F177"}.fa-long-arrow-right:before{content:"\\F178"}.fa-apple:before{content:"\\F179"}.fa-windows:before{content:"\\F17A"}.fa-android:before{content:"\\F17B"}.fa-linux:before{content:"\\F17C"}.fa-dribbble:before{content:"\\F17D"}.fa-skype:before{content:"\\F17E"}.fa-foursquare:before{content:"\\F180"}.fa-trello:before{content:"\\F181"}.fa-female:before{content:"\\F182"}.fa-male:before{content:"\\F183"}.fa-gittip:before,.fa-gratipay:before{content:"\\F184"}.fa-sun-o:before{content:"\\F185"}.fa-moon-o:before{content:"\\F186"}.fa-archive:before{content:"\\F187"}.fa-bug:before{content:"\\F188"}.fa-vk:before{content:"\\F189"}.fa-weibo:before{content:"\\F18A"}.fa-renren:before{content:"\\F18B"}.fa-pagelines:before{content:"\\F18C"}.fa-stack-exchange:before{content:"\\F18D"}.fa-arrow-circle-o-right:before{content:"\\F18E"}.fa-arrow-circle-o-left:before{content:"\\F190"}.fa-caret-square-o-left:before,.fa-toggle-left:before{content:"\\F191"}.fa-dot-circle-o:before{content:"\\F192"}.fa-wheelchair:before{content:"\\F193"}.fa-vimeo-square:before{content:"\\F194"}.fa-try:before,.fa-turkish-lira:before{content:"\\F195"}.fa-plus-square-o:before{content:"\\F196"}.fa-space-shuttle:before{content:"\\F197"}.fa-slack:before{content:"\\F198"}.fa-envelope-square:before{content:"\\F199"}.fa-wordpress:before{content:"\\F19A"}.fa-openid:before{content:"\\F19B"}.fa-bank:before,.fa-institution:before,.fa-university:before{content:"\\F19C"}.fa-graduation-cap:before,.fa-mortar-board:before{content:"\\F19D"}.fa-yahoo:before{content:"\\F19E"}.fa-google:before{content:"\\F1A0"}.fa-reddit:before{content:"\\F1A1"}.fa-reddit-square:before{content:"\\F1A2"}.fa-stumbleupon-circle:before{content:"\\F1A3"}.fa-stumbleupon:before{content:"\\F1A4"}.fa-delicious:before{content:"\\F1A5"}.fa-digg:before{content:"\\F1A6"}.fa-pied-piper-pp:before{content:"\\F1A7"}.fa-pied-piper-alt:before{content:"\\F1A8"}.fa-drupal:before{content:"\\F1A9"}.fa-joomla:before{content:"\\F1AA"}.fa-language:before{content:"\\F1AB"}.fa-fax:before{content:"\\F1AC"}.fa-building:before{content:"\\F1AD"}.fa-child:before{content:"\\F1AE"}.fa-paw:before{content:"\\F1B0"}.fa-spoon:before{content:"\\F1B1"}.fa-cube:before{content:"\\F1B2"}.fa-cubes:before{content:"\\F1B3"}.fa-behance:before{content:"\\F1B4"}.fa-behance-square:before{content:"\\F1B5"}.fa-steam:before{content:"\\F1B6"}.fa-steam-square:before{content:"\\F1B7"}.fa-recycle:before{content:"\\F1B8"}.fa-automobile:before,.fa-car:before{content:"\\F1B9"}.fa-cab:before,.fa-taxi:before{content:"\\F1BA"}.fa-tree:before{content:"\\F1BB"}.fa-spotify:before{content:"\\F1BC"}.fa-deviantart:before{content:"\\F1BD"}.fa-soundcloud:before{content:"\\F1BE"}.fa-database:before{content:"\\F1C0"}.fa-file-pdf-o:before{content:"\\F1C1"}.fa-file-word-o:before{content:"\\F1C2"}.fa-file-excel-o:before{content:"\\F1C3"}.fa-file-powerpoint-o:before{content:"\\F1C4"}.fa-file-image-o:before,.fa-file-photo-o:before,.fa-file-picture-o:before{content:"\\F1C5"}.fa-file-archive-o:before,.fa-file-zip-o:before{content:"\\F1C6"}.fa-file-audio-o:before,.fa-file-sound-o:before{content:"\\F1C7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\\F1C8"}.fa-file-code-o:before{content:"\\F1C9"}.fa-vine:before{content:"\\F1CA"}.fa-codepen:before{content:"\\F1CB"}.fa-jsfiddle:before{content:"\\F1CC"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-ring:before,.fa-life-saver:before,.fa-support:before{content:"\\F1CD"}.fa-circle-o-notch:before{content:"\\F1CE"}.fa-ra:before,.fa-rebel:before,.fa-resistance:before{content:"\\F1D0"}.fa-empire:before,.fa-ge:before{content:"\\F1D1"}.fa-git-square:before{content:"\\F1D2"}.fa-git:before{content:"\\F1D3"}.fa-hacker-news:before,.fa-y-combinator-square:before,.fa-yc-square:before{content:"\\F1D4"}.fa-tencent-weibo:before{content:"\\F1D5"}.fa-qq:before{content:"\\F1D6"}.fa-wechat:before,.fa-weixin:before{content:"\\F1D7"}.fa-paper-plane:before,.fa-send:before{content:"\\F1D8"}.fa-paper-plane-o:before,.fa-send-o:before{content:"\\F1D9"}.fa-history:before{content:"\\F1DA"}.fa-circle-thin:before{content:"\\F1DB"}.fa-header:before{content:"\\F1DC"}.fa-paragraph:before{content:"\\F1DD"}.fa-sliders:before{content:"\\F1DE"}.fa-share-alt:before{content:"\\F1E0"}.fa-share-alt-square:before{content:"\\F1E1"}.fa-bomb:before{content:"\\F1E2"}.fa-futbol-o:before,.fa-soccer-ball-o:before{content:"\\F1E3"}.fa-tty:before{content:"\\F1E4"}.fa-binoculars:before{content:"\\F1E5"}.fa-plug:before{content:"\\F1E6"}.fa-slideshare:before{content:"\\F1E7"}.fa-twitch:before{content:"\\F1E8"}.fa-yelp:before{content:"\\F1E9"}.fa-newspaper-o:before{content:"\\F1EA"}.fa-wifi:before{content:"\\F1EB"}.fa-calculator:before{content:"\\F1EC"}.fa-paypal:before{content:"\\F1ED"}.fa-google-wallet:before{content:"\\F1EE"}.fa-cc-visa:before{content:"\\F1F0"}.fa-cc-mastercard:before{content:"\\F1F1"}.fa-cc-discover:before{content:"\\F1F2"}.fa-cc-amex:before{content:"\\F1F3"}.fa-cc-paypal:before{content:"\\F1F4"}.fa-cc-stripe:before{content:"\\F1F5"}.fa-bell-slash:before{content:"\\F1F6"}.fa-bell-slash-o:before{content:"\\F1F7"}.fa-trash:before{content:"\\F1F8"}.fa-copyright:before{content:"\\F1F9"}.fa-at:before{content:"\\F1FA"}.fa-eyedropper:before{content:"\\F1FB"}.fa-paint-brush:before{content:"\\F1FC"}.fa-birthday-cake:before{content:"\\F1FD"}.fa-area-chart:before{content:"\\F1FE"}.fa-pie-chart:before{content:"\\F200"}.fa-line-chart:before{content:"\\F201"}.fa-lastfm:before{content:"\\F202"}.fa-lastfm-square:before{content:"\\F203"}.fa-toggle-off:before{content:"\\F204"}.fa-toggle-on:before{content:"\\F205"}.fa-bicycle:before{content:"\\F206"}.fa-bus:before{content:"\\F207"}.fa-ioxhost:before{content:"\\F208"}.fa-angellist:before{content:"\\F209"}.fa-cc:before{content:"\\F20A"}.fa-ils:before,.fa-shekel:before,.fa-sheqel:before{content:"\\F20B"}.fa-meanpath:before{content:"\\F20C"}.fa-buysellads:before{content:"\\F20D"}.fa-connectdevelop:before{content:"\\F20E"}.fa-dashcube:before{content:"\\F210"}.fa-forumbee:before{content:"\\F211"}.fa-leanpub:before{content:"\\F212"}.fa-sellsy:before{content:"\\F213"}.fa-shirtsinbulk:before{content:"\\F214"}.fa-simplybuilt:before{content:"\\F215"}.fa-skyatlas:before{content:"\\F216"}.fa-cart-plus:before{content:"\\F217"}.fa-cart-arrow-down:before{content:"\\F218"}.fa-diamond:before{content:"\\F219"}.fa-ship:before{content:"\\F21A"}.fa-user-secret:before{content:"\\F21B"}.fa-motorcycle:before{content:"\\F21C"}.fa-street-view:before{content:"\\F21D"}.fa-heartbeat:before{content:"\\F21E"}.fa-venus:before{content:"\\F221"}.fa-mars:before{content:"\\F222"}.fa-mercury:before{content:"\\F223"}.fa-intersex:before,.fa-transgender:before{content:"\\F224"}.fa-transgender-alt:before{content:"\\F225"}.fa-venus-double:before{content:"\\F226"}.fa-mars-double:before{content:"\\F227"}.fa-venus-mars:before{content:"\\F228"}.fa-mars-stroke:before{content:"\\F229"}.fa-mars-stroke-v:before{content:"\\F22A"}.fa-mars-stroke-h:before{content:"\\F22B"}.fa-neuter:before{content:"\\F22C"}.fa-genderless:before{content:"\\F22D"}.fa-facebook-official:before{content:"\\F230"}.fa-pinterest-p:before{content:"\\F231"}.fa-whatsapp:before{content:"\\F232"}.fa-server:before{content:"\\F233"}.fa-user-plus:before{content:"\\F234"}.fa-user-times:before{content:"\\F235"}.fa-bed:before,.fa-hotel:before{content:"\\F236"}.fa-viacoin:before{content:"\\F237"}.fa-train:before{content:"\\F238"}.fa-subway:before{content:"\\F239"}.fa-medium:before{content:"\\F23A"}.fa-y-combinator:before,.fa-yc:before{content:"\\F23B"}.fa-optin-monster:before{content:"\\F23C"}.fa-opencart:before{content:"\\F23D"}.fa-expeditedssl:before{content:"\\F23E"}.fa-battery-4:before,.fa-battery-full:before,.fa-battery:before{content:"\\F240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\\F241"}.fa-battery-2:before,.fa-battery-half:before{content:"\\F242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\\F243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\\F244"}.fa-mouse-pointer:before{content:"\\F245"}.fa-i-cursor:before{content:"\\F246"}.fa-object-group:before{content:"\\F247"}.fa-object-ungroup:before{content:"\\F248"}.fa-sticky-note:before{content:"\\F249"}.fa-sticky-note-o:before{content:"\\F24A"}.fa-cc-jcb:before{content:"\\F24B"}.fa-cc-diners-club:before{content:"\\F24C"}.fa-clone:before{content:"\\F24D"}.fa-balance-scale:before{content:"\\F24E"}.fa-hourglass-o:before{content:"\\F250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\\F251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\\F252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\\F253"}.fa-hourglass:before{content:"\\F254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\\F255"}.fa-hand-paper-o:before,.fa-hand-stop-o:before{content:"\\F256"}.fa-hand-scissors-o:before{content:"\\F257"}.fa-hand-lizard-o:before{content:"\\F258"}.fa-hand-spock-o:before{content:"\\F259"}.fa-hand-pointer-o:before{content:"\\F25A"}.fa-hand-peace-o:before{content:"\\F25B"}.fa-trademark:before{content:"\\F25C"}.fa-registered:before{content:"\\F25D"}.fa-creative-commons:before{content:"\\F25E"}.fa-gg:before{content:"\\F260"}.fa-gg-circle:before{content:"\\F261"}.fa-tripadvisor:before{content:"\\F262"}.fa-odnoklassniki:before{content:"\\F263"}.fa-odnoklassniki-square:before{content:"\\F264"}.fa-get-pocket:before{content:"\\F265"}.fa-wikipedia-w:before{content:"\\F266"}.fa-safari:before{content:"\\F267"}.fa-chrome:before{content:"\\F268"}.fa-firefox:before{content:"\\F269"}.fa-opera:before{content:"\\F26A"}.fa-internet-explorer:before{content:"\\F26B"}.fa-television:before,.fa-tv:before{content:"\\F26C"}.fa-contao:before{content:"\\F26D"}.fa-500px:before{content:"\\F26E"}.fa-amazon:before{content:"\\F270"}.fa-calendar-plus-o:before{content:"\\F271"}.fa-calendar-minus-o:before{content:"\\F272"}.fa-calendar-times-o:before{content:"\\F273"}.fa-calendar-check-o:before{content:"\\F274"}.fa-industry:before{content:"\\F275"}.fa-map-pin:before{content:"\\F276"}.fa-map-signs:before{content:"\\F277"}.fa-map-o:before{content:"\\F278"}.fa-map:before{content:"\\F279"}.fa-commenting:before{content:"\\F27A"}.fa-commenting-o:before{content:"\\F27B"}.fa-houzz:before{content:"\\F27C"}.fa-vimeo:before{content:"\\F27D"}.fa-black-tie:before{content:"\\F27E"}.fa-fonticons:before{content:"\\F280"}.fa-reddit-alien:before{content:"\\F281"}.fa-edge:before{content:"\\F282"}.fa-credit-card-alt:before{content:"\\F283"}.fa-codiepie:before{content:"\\F284"}.fa-modx:before{content:"\\F285"}.fa-fort-awesome:before{content:"\\F286"}.fa-usb:before{content:"\\F287"}.fa-product-hunt:before{content:"\\F288"}.fa-mixcloud:before{content:"\\F289"}.fa-scribd:before{content:"\\F28A"}.fa-pause-circle:before{content:"\\F28B"}.fa-pause-circle-o:before{content:"\\F28C"}.fa-stop-circle:before{content:"\\F28D"}.fa-stop-circle-o:before{content:"\\F28E"}.fa-shopping-bag:before{content:"\\F290"}.fa-shopping-basket:before{content:"\\F291"}.fa-hashtag:before{content:"\\F292"}.fa-bluetooth:before{content:"\\F293"}.fa-bluetooth-b:before{content:"\\F294"}.fa-percent:before{content:"\\F295"}.fa-gitlab:before{content:"\\F296"}.fa-wpbeginner:before{content:"\\F297"}.fa-wpforms:before{content:"\\F298"}.fa-envira:before{content:"\\F299"}.fa-universal-access:before{content:"\\F29A"}.fa-wheelchair-alt:before{content:"\\F29B"}.fa-question-circle-o:before{content:"\\F29C"}.fa-blind:before{content:"\\F29D"}.fa-audio-description:before{content:"\\F29E"}.fa-volume-control-phone:before{content:"\\F2A0"}.fa-braille:before{content:"\\F2A1"}.fa-assistive-listening-systems:before{content:"\\F2A2"}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before{content:"\\F2A3"}.fa-deaf:before,.fa-deafness:before,.fa-hard-of-hearing:before{content:"\\F2A4"}.fa-glide:before{content:"\\F2A5"}.fa-glide-g:before{content:"\\F2A6"}.fa-sign-language:before,.fa-signing:before{content:"\\F2A7"}.fa-low-vision:before{content:"\\F2A8"}.fa-viadeo:before{content:"\\F2A9"}.fa-viadeo-square:before{content:"\\F2AA"}.fa-snapchat:before{content:"\\F2AB"}.fa-snapchat-ghost:before{content:"\\F2AC"}.fa-snapchat-square:before{content:"\\F2AD"}.fa-pied-piper:before{content:"\\F2AE"}.fa-first-order:before{content:"\\F2B0"}.fa-yoast:before{content:"\\F2B1"}.fa-themeisle:before{content:"\\F2B2"}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:"\\F2B3"}.fa-fa:before,.fa-font-awesome:before{content:"\\F2B4"}.fa-handshake-o:before{content:"\\F2B5"}.fa-envelope-open:before{content:"\\F2B6"}.fa-envelope-open-o:before{content:"\\F2B7"}.fa-linode:before{content:"\\F2B8"}.fa-address-book:before{content:"\\F2B9"}.fa-address-book-o:before{content:"\\F2BA"}.fa-address-card:before,.fa-vcard:before{content:"\\F2BB"}.fa-address-card-o:before,.fa-vcard-o:before{content:"\\F2BC"}.fa-user-circle:before{content:"\\F2BD"}.fa-user-circle-o:before{content:"\\F2BE"}.fa-user-o:before{content:"\\F2C0"}.fa-id-badge:before{content:"\\F2C1"}.fa-drivers-license:before,.fa-id-card:before{content:"\\F2C2"}.fa-drivers-license-o:before,.fa-id-card-o:before{content:"\\F2C3"}.fa-quora:before{content:"\\F2C4"}.fa-free-code-camp:before{content:"\\F2C5"}.fa-telegram:before{content:"\\F2C6"}.fa-thermometer-4:before,.fa-thermometer-full:before,.fa-thermometer:before{content:"\\F2C7"}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:"\\F2C8"}.fa-thermometer-2:before,.fa-thermometer-half:before{content:"\\F2C9"}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:"\\F2CA"}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:"\\F2CB"}.fa-shower:before{content:"\\F2CC"}.fa-bath:before,.fa-bathtub:before,.fa-s15:before{content:"\\F2CD"}.fa-podcast:before{content:"\\F2CE"}.fa-window-maximize:before{content:"\\F2D0"}.fa-window-minimize:before{content:"\\F2D1"}.fa-window-restore:before{content:"\\F2D2"}.fa-times-rectangle:before,.fa-window-close:before{content:"\\F2D3"}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:"\\F2D4"}.fa-bandcamp:before{content:"\\F2D5"}.fa-grav:before{content:"\\F2D6"}.fa-etsy:before{content:"\\F2D7"}.fa-imdb:before{content:"\\F2D8"}.fa-ravelry:before{content:"\\F2D9"}.fa-eercast:before{content:"\\F2DA"}.fa-microchip:before{content:"\\F2DB"}.fa-snowflake-o:before{content:"\\F2DC"}.fa-superpowers:before{content:"\\F2DD"}.fa-wpexplorer:before{content:"\\F2DE"}.fa-meetup:before{content:"\\F2E0"}.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}',""]); +},function(A,M,t){M=A.exports=t(136)(),M.push([A.id,'*,:after,:before{box-sizing:border-box}body{font-family:Lato,sans-serif;font-size:15px;line-height:1.42857143;color:#8e8e8e;background-color:#edecec}button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#46a5e0}a,a:focus,a:hover{text-decoration:none}a:focus,a:hover{color:#1f7fba}a:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.img-responsive{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{padding:4px;line-height:1.42857143;background-color:#edecec;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;transition:all .2s ease-in-out;display:inline-block;max-width:100%;height:auto}.img-circle{border-radius:50%}hr{margin-top:21px;margin-bottom:21px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;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}[role=button]{cursor:pointer}.container{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}.row{margin-left:-15px;margin-right:-15px}.col-lg-1,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-10,.col-lg-11,.col-lg-12,.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11,.col-md-12,.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12,.col-xs-1,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-10,.col-xs-11,.col-xs-12{position:relative;min-height:1px;padding-left:15px;padding-right:15px}.col-xs-1,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-10,.col-xs-11,.col-xs-12{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11,.col-md-12{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-10,.col-lg-11,.col-lg-12{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-top:4px solid\\9;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown,.dropup{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;font-size:15px;text-align:left;background-color:#fff;border:1px solid transparent;border-radius:4px;box-shadow:0 6px 12px rgba(0,0,0,.175);background-clip:padding-box}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9.5px 0;overflow:hidden;background-color:rgba(0,0,0,.08)}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#8e8e8e;white-space:nowrap}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{text-decoration:none;color:#333;background-color:rgba(0,0,0,.05)}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{color:#333;text-decoration:none;outline:0;background-color:rgba(0,0,0,.075)}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{color:#e4e4e4}.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{text-decoration:none;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);cursor:not-allowed}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{left:auto;right:0}.dropdown-menu-left{left:0;right:auto}.dropdown-header{display:block;padding:3px 20px;font-size:13px;line-height:1.42857143;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;left:0;right:0;bottom:0;top:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px dashed;border-bottom:4px solid\\9;content:""}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:768px){.navbar-right .dropdown-menu{left:auto;right:0}.navbar-right .dropdown-menu>li>a{text-align:right}.navbar-right .dropdown-menu-left{left:0;right:auto}}.modal,.modal-open{overflow:hidden}.modal{display:none;position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transform:translateY(-25%);transform:translateY(-25%);-webkit-transition:-webkit-transform .3s ease-out;transition:transform .3s ease-out}.modal.in .modal-dialog{-webkit-transform:translate(0);transform:translate(0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;border:1px solid transparent;border-radius:6px;box-shadow:0 3px 9px rgba(0,0,0,.5);background-clip:padding-box;outline:0}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:rgba(0,0,0,.1)}.modal-backdrop.fade{opacity:0;filter:alpha(opacity=0)}.modal-backdrop.in{opacity:.5;filter:alpha(opacity=50)}.modal-header{padding:30px 35px 0;border-bottom:1px solid transparent}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:transparent}.modal-body{position:relative;padding:30px 35px}.modal-footer{padding:30px 35px;text-align:right;border-top:1px solid transparent}.modal-footer .btn+.btn{margin-left:5px;margin-bottom:0}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:400px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-family:Lato,sans-serif;font-style:normal;font-weight:400;letter-spacing:normal;line-break:auto;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;white-space:normal;word-break:normal;word-spacing:normal;word-wrap:normal;font-size:13px;opacity:0;filter:alpha(opacity=0)}.tooltip.in{opacity:.9;filter:alpha(opacity=90)}.tooltip.top{margin-top:-3px;padding:5px 0}.tooltip.right{margin-left:3px;padding:0 5px}.tooltip.bottom{margin-top:3px;padding:5px 0}.tooltip.left{margin-left:-3px;padding:0 5px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{right:5px}.tooltip.top-left .tooltip-arrow,.tooltip.top-right .tooltip-arrow{bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{left:5px}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}@-ms-viewport{width:device-width}.visible-lg,.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block,.visible-md,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-sm,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-xs,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table!important}tr.visible-xs{display:table-row!important}td.visible-xs,th.visible-xs{display:table-cell!important}}@media (max-width:767px){.visible-xs-block{display:block!important}}@media (max-width:767px){.visible-xs-inline{display:inline!important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table!important}tr.visible-sm{display:table-row!important}td.visible-sm,th.visible-sm{display:table-cell!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table!important}tr.visible-md{display:table-row!important}td.visible-md,th.visible-md{display:table-cell!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table!important}tr.visible-lg{display:table-row!important}td.visible-lg,th.visible-lg{display:table-cell!important}}@media (min-width:1200px){.visible-lg-block{display:block!important}}@media (min-width:1200px){.visible-lg-inline{display:inline!important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block!important}}@media (max-width:767px){.hidden-xs{display:none!important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}}@media (min-width:1200px){.hidden-lg{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table!important}tr.visible-print{display:table-row!important}td.visible-print,th.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}}*{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}:active,:focus{outline:0}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body,html{min-height:100%}a{-webkit-transition:color;transition:color;-webkit-transition-duration:.3s;transition-duration:.3s}button{border:0}.animated.infinite{-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite}@-webkit-keyframes fadeIn{0%{opacity:0}to{opacity:1}}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.fadeIn{-webkit-animation-name:fadeIn;animation-name:fadeIn}@-webkit-keyframes fadeInDown{0%{opacity:0;-webkit-transform:translateY(-20px)}to{opacity:1;-webkit-transform:translateY(0)}}@keyframes fadeInDown{0%{opacity:0;transform:translateY(-20px)}to{opacity:1;transform:translateY(0)}}.fadeInDown{-webkit-animation-name:fadeInDown;animation-name:fadeInDown}@-webkit-keyframes fadeInUp{0%{opacity:0;-webkit-transform:translateY(20px)}to{opacity:1;-webkit-transform:translateY(0)}}@keyframes fadeInUp{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.fadeInUp{-webkit-animation-name:fadeInUp;animation-name:fadeInUp}@-webkit-keyframes fadeOut{0%{opacity:1}to{opacity:0}}@keyframes fadeOut{0%{opacity:1}to{opacity:0}}.fadeOut{-webkit-animation-name:fadeOut;animation-name:fadeOut}@-webkit-keyframes fadeOutDown{0%{opacity:1;-webkit-transform:translateY(0)}to{opacity:0;-webkit-transform:translateY(20px)}}@keyframes fadeOutDown{0%{opacity:1;transform:translateY(0)}to{opacity:0;transform:translateY(20px)}}.fadeOutDown{-webkit-animation-name:fadeOutDown;animation-name:fadeOutDown}@-webkit-keyframes fadeOutUp{0%{opacity:1;-webkit-transform:translateY(0)}to{opacity:0;-webkit-transform:translateY(-20px)}}@keyframes fadeOutUp{0%{opacity:1;transform:translateY(0)}to{opacity:0;transform:translateY(-20px)}}.fadeOutUp{-webkit-animation-name:fadeOutUp;animation-name:fadeOutUp}@-webkit-keyframes zoomIn{0%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}50%{opacity:1}}@keyframes zoomIn{0%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}50%{opacity:1}}.text-center{text-align:center!important}.text-left{text-align:left!important}.text-right{text-align:right!important}.clearfix:after,.clearfix:before,.container-fluid:after,.container-fluid:before,.container:after,.container:before,.modal-footer:after,.modal-footer:before,.modal-header:after,.modal-header:before,.row:after,.row:before{content:" ";display:table}.clearfix:after,.container-fluid:after,.container:after,.modal-footer:after,.modal-header:after,.row:after{clear:both}.pull-right{float:right!important}.pull-left{float:left!important}.p-relative{position:relative}.m-0{margin:0!important}.m-t-0{margin-top:0!important}.m-b-0{margin-bottom:0!important}.m-l-0{margin-left:0!important}.m-r-0{margin-right:0!important}.m-5{margin:5px!important}.m-t-5{margin-top:5px!important}.m-b-5{margin-bottom:5px!important}.m-l-5{margin-left:5px!important}.m-r-5{margin-right:5px!important}.m-10{margin:10px!important}.m-t-10{margin-top:10px!important}.m-b-10{margin-bottom:10px!important}.m-l-10{margin-left:10px!important}.m-r-10{margin-right:10px!important}.m-15{margin:15px!important}.m-t-15{margin-top:15px!important}.m-b-15{margin-bottom:15px!important}.m-l-15{margin-left:15px!important}.m-r-15{margin-right:15px!important}.m-20{margin:20px!important}.m-t-20{margin-top:20px!important}.m-b-20{margin-bottom:20px!important}.m-l-20{margin-left:20px!important}.m-r-20{margin-right:20px!important}.m-25{margin:25px!important}.m-t-25{margin-top:25px!important}.m-b-25{margin-bottom:25px!important}.m-l-25{margin-left:25px!important}.m-r-25{margin-right:25px!important}.m-30{margin:30px!important}.m-t-30{margin-top:30px!important}.m-b-30{margin-bottom:30px!important}.m-l-30{margin-left:30px!important}.m-r-30{margin-right:30px!important}.p-0{padding:0!important}.p-t-0{padding-top:0!important}.p-b-0{padding-bottom:0!important}.p-l-0{padding-left:0!important}.p-r-0{padding-right:0!important}.p-5{padding:5px!important}.p-t-5{padding-top:5px!important}.p-b-5{padding-bottom:5px!important}.p-l-5{padding-left:5px!important}.p-r-5{padding-right:5px!important}.p-10{padding:10px!important}.p-t-10{padding-top:10px!important}.p-b-10{padding-bottom:10px!important}.p-l-10{padding-left:10px!important}.p-r-10{padding-right:10px!important}.p-15{padding:15px!important}.p-t-15{padding-top:15px!important}.p-b-15{padding-bottom:15px!important}.p-l-15{padding-left:15px!important}.p-r-15{padding-right:15px!important}.p-20{padding:20px!important}.p-t-20{padding-top:20px!important}.p-b-20{padding-bottom:20px!important}.p-l-20{padding-left:20px!important}.p-r-20{padding-right:20px!important}.p-25{padding:25px!important}.p-t-25{padding-top:25px!important}.p-b-25{padding-bottom:25px!important}.p-l-25{padding-left:25px!important}.p-r-25{padding-right:25px!important}.p-30{padding:30px!important}.p-t-30{padding-top:30px!important}.p-b-30{padding-bottom:30px!important}.p-l-30{padding-left:30px!important}.p-r-30{padding-right:30px!important}@font-face{font-family:Lato;src:url('+t(514)+') format("woff2"),url('+t(513)+') format("woff");font-weight:400;font-style:normal}.form-control{border:0;border-bottom:1px solid #eee;color:#32393f;padding:5px;width:100%;font-size:13px;background-color:transparent}select.form-control{-webkit-appearance:none;-moz-appearance:none;border-radius:0;background:url('+t(517)+') no-repeat bottom 7px right}.input-group{position:relative}.input-group:not(:last-child){margin-bottom:25px}.input-group label:not(.ig-label){font-size:13px;display:block;margin-bottom:10px}.ig-label{position:absolute;text-align:center;bottom:7px;left:0;width:100%;-webkit-transition:all;transition:all;-webkit-transition-duration:.25s;transition-duration:.25s;padding:2px 0 3px;border-radius:2px;font-weight:400}.ig-helpers{z-index:1;width:100%;left:0}.ig-helpers,.ig-helpers:after,.ig-helpers:before{position:absolute;height:2px;bottom:0}.ig-helpers:after,.ig-helpers:before{content:"";width:0;-webkit-transition:all;transition:all;-webkit-transition-duration:.25s;transition-duration:.25s;background-color:#03a9f4}.ig-helpers:before{left:50%}.ig-helpers:after{right:50%}.ig-text{width:100%;height:40px;border:0;background:transparent;text-align:center;position:relative;z-index:1;border-bottom:1px solid #eee;color:#32393f;font-size:13px}.ig-text:focus+.ig-helpers:after,.ig-text:focus+.ig-helpers:before{width:50%}.ig-text:disabled~.ig-label,.ig-text:focus~.ig-label,.ig-text:valid~.ig-label{bottom:35px;font-size:13px;z-index:1}.ig-text:disabled{opacity:.5;filter:alpha(opacity=50)}.ig-dark .ig-text{color:#fff;border-color:hsla(0,0%,100%,.1)}.ig-dark .ig-helpers:after,.ig-dark .ig-helpers:before{background-color:#dfdfdf;height:1px}.ig-left .ig-label,.ig-left .ig-text{text-align:left}.ig-error .ig-label{color:#e23f3f}.ig-error .ig-helpers i:first-child,.ig-error .ig-helpers i:first-child:after,.ig-error .ig-helpers i:first-child:before{background:rgba(226,63,63,.43)}.ig-error .ig-helpers i:last-child,.ig-error .ig-helpers i:last-child:after,.ig-error .ig-helpers i:last-child:before{background:#e23f3f!important}.ig-error:after{content:"\\F05A";font-family:FontAwesome;position:absolute;top:17px;right:9px;font-size:20px;color:#d33d3e}.ig-search:before{font-family:fontAwesome;content:"\\F002";font-size:15px;position:absolute;left:2px;top:8px}.ig-search .ig-text{padding-left:25px}.set-expire{border:1px solid #eee;margin:35px 0 30px}.set-expire-item{padding:9px 5px 3px;position:relative;display:table-cell;width:1%;text-align:center}.set-expire-item:not(:last-child){border-right:1px solid #eee}.set-expire-title{font-size:10px;text-transform:uppercase}.set-expire-value{display:inline-block;overflow:hidden;position:relative;left:-8px}.set-expire-value input{font-size:20px;text-align:center;position:relative;right:-15px;border:0;color:#333;padding:0;height:25px;width:100%;font-weight:400}.set-expire-decrease,.set-expire-increase{position:absolute;width:20px;height:20px;background:url('+t(515)+') no-repeat 50%;background-size:85%;left:50%;margin-left:-10px;opacity:.2;filter:alpha(opacity=20);cursor:pointer}.set-expire-decrease:hover,.set-expire-increase:hover{opacity:.5;filter:alpha(opacity=50)}.set-expire-increase{top:-25px}.set-expire-decrease{bottom:-27px;-webkit-transform:rotate(-180deg);transform:rotate(-180deg)}.btn{border:0;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:2px;text-align:center;-webkit-transition:all;transition:all;-webkit-transition-duration:.3s;transition-duration:.3s}.btn:focus,.btn:hover{opacity:.9;filter:alpha(opacity=90)}.btn-block{display:block;width:100%}.btn-white{color:#5b5b5b;background-color:#fff}.btn-white:focus,.btn-white:hover{color:#5b5b5b;background-color:#f0f0f0}.btn-link{color:#545454;background-color:#eee}.btn-link:focus,.btn-link:hover{color:#545454;background-color:#dfdfdf}.btn-danger{color:#fff;background-color:#ff726f}.btn-danger:focus,.btn-danger:hover{color:#fff;background-color:#ff5450}.btn-primary{color:#fff;background-color:#50b2ff}.btn-primary:focus,.btn-primary:hover{color:#fff;background-color:#31a5ff}.btn-success{color:#fff;background-color:#33d46f}.btn-success:focus,.btn-success:hover{color:#fff;background-color:#28c061}.close{right:15px;font-weight:400;opacity:1;font-size:18px;position:absolute;text-align:center;top:16px;z-index:1;padding:0;border:0;background-color:transparent}.close span{width:25px;height:25px;display:block;border-radius:50%;line-height:24px;text-shadow:none}.close:not(.close-alt) span{background-color:hsla(0,0%,100%,.1);color:hsla(0,0%,100%,.8)}.close:not(.close-alt):focus span,.close:not(.close-alt):hover span{background-color:hsla(0,0%,100%,.2);color:#fff}.close-alt span{background-color:#efefef;color:#989898}.close-alt:focus span,.close-alt:hover span{background-color:#e8e8e8;color:#7b7b7b}.hidden{display:none!important}.copy-text input{width:100%;border-radius:1px;border:1px solid #eee;padding:7px 12px;font-size:13px;line-height:100%;cursor:text;-webkit-transition:border-color;transition:border-color;-webkit-transition-duration:.3s;transition-duration:.3s}.copy-text input:hover{border-color:#e1e1e1}.share-availability{margin-bottom:40px}.share-availability:after,.share-availability:before{position:absolute;bottom:-30px;font-size:10px}.share-availability:before{content:"01 Sec";left:0}.share-availability:after{content:"7 days";right:0}.modal-aheader{height:100px}.modal-aheader:before{height:0!important}.modal-aheader .modal-dialog{margin:0;vertical-align:top}.login{height:100vh;min-height:500px;background:#32393f;text-align:center}.login:before{height:calc(100% - 110px);width:1px;content:""}.l-wrap,.login:before{display:inline-block;vertical-align:middle}.l-wrap{width:80%;max-width:500px;margin-top:-50px}.l-wrap.toggled{display:inline-block}.l-wrap .input-group:not(:last-child){margin-bottom:40px}.l-footer{height:110px;padding:0 50px}.lf-logo{float:right}.lf-logo img{width:40px}.lf-server{float:left;color:hsla(0,0%,100%,.4);font-size:20px;font-weight:400;padding-top:40px}@media (max-width:768px){.lf-logo,.lf-server{float:none;display:block;text-align:center;width:100%}.lf-logo{margin-bottom:5px}.lf-server{font-size:15px}}.lw-btn{width:50px;height:50px;border:1px solid #fff;display:inline-block;border-radius:50%;font-size:22px;color:#fff;-webkit-transition:all;transition:all;-webkit-transition-duration:.3s;transition-duration:.3s;opacity:.3;background-color:transparent;line-height:45px;padding:0}.lw-btn:hover{color:#fff;opacity:.8;border-color:#fff}.lw-btn i{display:block;width:100%;padding-left:3px}input:-webkit-autofill{-webkit-box-shadow:0 0 0 50px #32393f inset!important;-webkit-text-fill-color:#fff!important}@media (min-width:668px){.fe-header{position:relative;padding:40px 40px 20px 45px}}@media (max-width:667px){.fe-header{padding:20px}}.fe-header h2{font-size:16px;font-weight:400;margin:0}.fe-header h2>span{margin-bottom:7px;display:inline-block}.fe-header h2>span:not(:first-child):before{content:"/";margin:0 4px;color:#8e8e8e}.fe-header p{margin-top:7px}.feh-usage{margin-top:12px;max-width:285px}@media (max-width:667px){.feh-usage{max-width:100%;font-size:12px}}.feh-usage>ul{margin-top:7px;list-style:none;padding:0}.feh-usage>ul>li{padding-right:0;display:inline-block}.fehu-chart{height:5px;background:#eee;position:relative;border-radius:2px;overflow:hidden}.fehu-chart>div{position:absolute;left:0;height:100%;background:#46a5e0}.feh-actions{list-style:none;padding:0;margin:0;position:absolute;right:35px;top:30px;z-index:11}@media (max-width:991px){.feh-actions{top:7px;right:10px;position:fixed}}.feh-actions>li{display:inline-block;text-align:right;vertical-align:top;line-height:100%}.feh-actions>li>.btn-group>button,.feh-actions>li>a{display:block;height:45px;min-width:45px;text-align:center;border-radius:50%;padding:0;border:0;background:none}@media (min-width:992px){.feh-actions>li>.btn-group>button,.feh-actions>li>a{color:#7b7b7b;font-size:21px;line-height:45px;-webkit-transition:all;transition:all;-webkit-transition-duration:.3s;transition-duration:.3s}.feh-actions>li>.btn-group>button:hover,.feh-actions>li>a:hover{background:rgba(0,0,0,.09)}}@media (max-width:991px){.feh-actions>li>.btn-group>button,.feh-actions>li>a{background:url('+t(235)+') no-repeat 50%}.feh-actions>li>.btn-group>button .fa-reorder,.feh-actions>li>a .fa-reorder{display:none}}@media (max-width:991px){.fe-header-mobile{background-color:#32393f;padding:10px 50px 9px 12px;text-align:center;position:fixed;z-index:10;box-shadow:0 0 10px rgba(0,0,0,.3);left:0;top:0;width:100%}.fe-header-mobile .mh-logo{height:35px;position:relative;top:4px}.feh-trigger{width:41px;height:41px;cursor:pointer;float:left;position:relative;text-align:center}.feh-trigger:after,.feh-trigger:before{content:"";position:absolute;top:0;left:0;width:100%;height:100%;border-radius:50%}.feh-trigger:after{z-index:1}.feh-trigger:before{background:hsla(0,0%,100%,.1);-webkit-transition:all;transition:all;-webkit-transition-duration:.3s;transition-duration:.3s;-webkit-transform:scale(0);transform:scale(0)}.feht-toggled:before{-webkit-transform:scale(1);transform:scale(1)}.feht-toggled .feht-lines{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.feht-toggled .feht-lines>div.top{width:12px;transform:translateX(8px) translateY(1px) rotate(45deg);-webkit-transform:translateX(8px) translateY(1px) rotate(45deg)}.feht-toggled .feht-lines>div.bottom{width:12px;transform:translateX(8px) translateY(-1px) rotate(-45deg);-webkit-transform:translateX(8px) translateY(-1px) rotate(-45deg)}.feht-lines,.feht-lines>div{-webkit-transition:all;transition:all;-webkit-transition-duration:.3s;transition-duration:.3s}.feht-lines{width:18px;height:12px;display:inline-block;margin-top:14px}.feht-lines>div{background-color:#eaeaea;width:18px;height:2px}.feht-lines>div.center{margin:3px 0}}.fe-sidebar{width:320px;background-color:#32393f;position:fixed;height:100%;overflow:hidden;padding:25px}@media (min-width:992px){.fe-sidebar{-webkit-transform:translateZ(0);transform:translateZ(0)}}@media (max-width:991px){.fe-sidebar{padding-top:85px;z-index:9;box-shadow:0 0 10px rgba(0,0,0,.65);-webkit-transition:all;transition:all;-webkit-transition-duration:.3s;transition-duration:.3s;-webkit-transform:translate3d(-335px,0,0);transform:translate3d(-335px,0,0)}.fe-sidebar.toggled{-webkit-transform:translateZ(0);transform:translateZ(0)}}.fe-sidebar a{color:hsla(0,0%,100%,.58)}.fe-sidebar a:hover{color:#fff}.fes-header{margin-bottom:40px}.fes-header h2,.fes-header img{float:left}.fes-header h2{margin:13px 0 0 10px;font-weight:400}.fes-header img{width:32px}.fesl-inner{height:calc(100vh - 260px);overflow:auto;padding:0;margin:0 -25px}.fesl-inner li{position:relative}.fesl-inner li>a{display:block;padding:10px 45px 12px 55px;word-wrap:break-word}.fesl-inner li>a:before{font-family:FontAwesome;content:"\\F0A0";font-size:17px;position:absolute;top:10px;left:25px;opacity:.8;filter:alpha(opacity=80)}.fesl-inner li>a.fesli-loading:before{content:"";width:20px;height:20px;border-radius:50%;-webkit-animation-name:zoomIn;animation-name:zoomIn;-webkit-animation-duration:.5s;animation-duration:.5s;-webkit-animation-fill-mode:both;animation-fill-mode:both;border:2px solid hsla(0,0%,100%,.1);border-bottom-color:hsla(0,0%,100%,.5);position:absolute;z-index:1;-webkit-animation:zoomIn .25s,spin .7s .25s infinite linear;animation:zoomIn .25s,spin .7s .25s infinite linear;left:32px;top:0;bottom:0;margin:auto}.fesl-inner li.active{background-color:#282e32}.fesl-inner li.active>a{color:#fff}.fesl-inner li:not(.active):hover{background-color:rgba(0,0,0,.1)}.fesl-inner li:not(.active):hover>a{color:#fff}.fesl-inner li:hover .fesli-trigger{opacity:.6;filter:alpha(opacity=60)}.fesl-inner li:hover .fesli-trigger:hover{opacity:1;filter:alpha(opacity=100)}.fesl-inner ul{list-style:none;padding:0;margin:0}.fesl-inner:hover .scrollbar-vertical{opacity:1}.fesli-trigger{filter:alpha(opacity=0);-webkit-transition:all;transition:all;-webkit-transition-duration:.2s;transition-duration:.2s;top:0;right:0;width:35px;cursor:pointer;background:url('+t(235)+') no-repeat top 20px left}.fesli-trigger,.scrollbar-vertical{opacity:0;position:absolute;height:100%}.scrollbar-vertical{right:5px;width:4px;-webkit-transition:opacity;transition:opacity;-webkit-transition-duration:.3s;transition-duration:.3s}.scrollbar-vertical div{border-radius:1px!important;background-color:#6a6a6a!important}.fes-host{position:fixed;left:0;bottom:0;z-index:1;background:#32393f;color:hsla(0,0%,100%,.4);font-size:15px;font-weight:400;width:320px;padding:20px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fes-host>i{margin-right:10px}.fesl-row{position:relative}@media (min-width:668px){.fesl-row{padding:5px 20px 5px 40px;display:flex;flex-flow:row nowrap;justify-content:space-between}}@media (max-width:667px){.fesl-row{padding:5px 20px}}.fesl-row:after,.fesl-row:before{content:" ";display:table}.fesl-row:after{clear:both}@media (min-width:668px){header.fesl-row{margin-bottom:20px;border-bottom:1px solid #f0f0f0;padding-left:30px}header.fesl-row .fesl-item,header.fesl-row .fesli-sort{-webkit-transition:all;transition:all;-webkit-transition-duration:.3s;transition-duration:.3s}header.fesl-row .fesl-item{cursor:pointer;color:#8e8e8e;font-weight:500;margin-bottom:-5px}header.fesl-row .fesl-item>.fesli-sort{float:right;margin:4px 0 0;opacity:0;filter:alpha(opacity=0);color:#32393f;font-size:14px}header.fesl-row .fesl-item:hover:not(.fesl-item-actions){background:#f5f5f5;color:#32393f}header.fesl-row .fesl-item:hover:not(.fesl-item-actions)>.fesli-sort{opacity:.5;filter:alpha(opacity=50)}}@media (max-width:667px){header.fesl-row{display:none}}div.fesl-row{border-bottom:1px solid transparent;cursor:default;-webkit-transition:background-color;transition:background-color;-webkit-transition-duration:.5s;transition-duration:.5s}@media (max-width:667px){div.fesl-row{padding:5px 20px}}div.fesl-row:not(.fesl-row-selected):nth-child(2n){background-color:#fafafa}div.fesl-row:hover .fis-icon:before{opacity:0;filter:alpha(opacity=0)}div.fesl-row:hover .fis-helper:before{opacity:1;filter:alpha(opacity=100)}div.fesl-row[data-type=folder] .fis-icon{background-color:#a1d6dd}div.fesl-row[data-type=folder] .fis-icon:before{content:"\\F114"}div.fesl-row[data-type=pdf] .fis-icon{background-color:#fa7775}div.fesl-row[data-type=pdf] .fis-icon:before{content:"\\F1C1"}div.fesl-row[data-type=zip] .fis-icon{background-color:#427089}div.fesl-row[data-type=zip] .fis-icon:before{content:"\\F1C6"}div.fesl-row[data-type=audio] .fis-icon{background-color:#009688}div.fesl-row[data-type=audio] .fis-icon:before{content:"\\F1C7"}div.fesl-row[data-type=code] .fis-icon{background-color:#997867}div.fesl-row[data-type=code] .fis-icon:before{content:"\\F1C9"}div.fesl-row[data-type=excel] .fis-icon{background-color:#f1c3 3}div.fesl-row[data-type=excel] .fis-icon:before{content:"\\F1C3"}div.fesl-row[data-type=image] .fis-icon{background-color:#f06292}div.fesl-row[data-type=image] .fis-icon:before{content:"\\F1C5"}div.fesl-row[data-type=video] .fis-icon{background-color:#f8c363}div.fesl-row[data-type=video] .fis-icon:before{content:"\\F1C8"}div.fesl-row[data-type=other] .fis-icon{background-color:#afafaf}div.fesl-row[data-type=other] .fis-icon:before{content:"\\F016"}div.fesl-row[data-type=text] .fis-icon{background-color:#8a8a8a}div.fesl-row[data-type=text] .fis-icon:before{content:"\\F0F6"}div.fesl-row[data-type=doc] .fis-icon{background-color:#2196f5}div.fesl-row[data-type=doc] .fis-icon:before{content:"\\F1C2"}div.fesl-row[data-type=presentation] .fis-icon{background-color:#896ea6}div.fesl-row[data-type=presentation] .fis-icon:before{content:"\\F1C4"}div.fesl-row.fesl-loading:before{content:""}div.fesl-row.fesl-loading:after{content:"";width:20px;height:20px;border-radius:50%;-webkit-animation-name:zoomIn;animation-name:zoomIn;-webkit-animation-duration:.5s;animation-duration:.5s;-webkit-animation-fill-mode:both;animation-fill-mode:both;border:2px solid hsla(0,0%,100%,.5);border-bottom-color:#fff;position:absolute;z-index:1;-webkit-animation:zoomIn .25s,spin .7s .25s infinite linear;animation:zoomIn .25s,spin .7s .25s infinite linear;left:57px;top:17px}@media (max-width:667px){div.fesl-row.fesl-loading:after{left:27px}}.fesl-row-selected{background-color:#fbf2bf}.fesl-row-selected,.fesl-row-selected .fesl-item a{color:#757575}.fi-select{float:left;position:relative;width:35px;height:35px;margin:3px 0}@media (max-width:667px){.fi-select{margin-right:15px}}.fi-select input{position:absolute;left:0;top:0;width:35px;height:35px;z-index:20;opacity:0;cursor:pointer}.fi-select input:checked~.fis-icon{background-color:#32393f}.fi-select input:checked~.fis-icon:before{opacity:0}.fi-select input:checked~.fis-helper:before{-webkit-transform:scale(0);transform:scale(0)}.fi-select input:checked~.fis-helper:after{-webkit-transform:scale(1);transform:scale(1)}.fis-icon{display:inline-block;vertical-align:top;border-radius:50%;width:35px;height:35px;-webkit-transition:background-color;transition:background-color;-webkit-transition-duration:.25s;transition-duration:.25s}.fis-icon:before{width:100%;height:100%;text-align:center;position:absolute;border-radius:50%;font-family:fontAwesome;line-height:35px;font-size:16px;color:#fff;-webkit-transition:all;transition:all;-webkit-transition-duration:.3s;transition-duration:.3s;font-style:normal}.fis-helper:after,.fis-helper:before{position:absolute;-webkit-transition:all;transition:all;-webkit-transition-duration:.25s;transition-duration:.25s}.fis-helper:before{content:"";width:15px;height:15px;border:2px solid #fff;z-index:10;border-radius:2px;top:10px;left:10px;opacity:0}.fis-helper:after{font-family:fontAwesome;content:"\\F00C";top:8px;left:9px;color:#fff;font-size:14px;-webkit-transform:scale(0);transform:scale(0)}.fesl-item{display:block}.fesl-item a{color:#818181}@media (min-width:668px){.fesl-item:not(.fesl-item-actions):not(.fesl-item-icon){text-overflow:ellipsis;padding:10px 15px;white-space:nowrap;overflow:hidden}.fesl-item.fesl-item-name{flex:3}.fesl-item.fesl-item-size{width:140px}.fesl-item.fesl-item-modified{width:190px}.fesl-item.fesl-item-actions{width:40px}}@media (max-width:667px){.fesl-item{padding:0}.fesl-item.fesl-item-name{width:100%;margin-bottom:3px}.fesl-item.fesl-item-modified,.fesl-item.fesl-item-size{font-size:12px;color:#b5b5b5;float:left}.fesl-item.fesl-item-modified{max-width:72px;white-space:nowrap;overflow:hidden}.fesl-item.fesl-item-size{margin-right:10px}.fesl-item.fesl-item-actions{position:absolute;top:5px;right:10px}}.fia-toggle{height:36px;width:36px;background:transparent url('+t(516)+') no-repeat 50%;position:relative;top:3px;opacity:.4;filter:alpha(opacity=40)}.fia-toggle:hover{opacity:.7;filter:alpha(opacity=70)}.fesl-item-actions .dropdown-menu{background-color:transparent;box-shadow:none;padding:0;right:38px;left:auto;margin:0;height:100%;text-align:right}.fesl-item-actions .dropdown.open .dropdown-menu .fiad-action{right:0}.fiad-action{height:35px;width:35px;background:#ffc107;display:inline-block;border-radius:50%;text-align:center;line-height:35px;font-weight:400;position:relative;top:4px;margin-left:5px;-webkit-animation-name:fiad-action-anim;animation-name:fiad-action-anim;-webkit-transform-origin:center center;transform-origin:center center;-webkit-backface-visibility:none;backface-visibility:none;box-shadow:0 2px 4px rgba(0,0,0,.1)}.fiad-action:nth-child(2){-webkit-animation-duration:.1s;animation-duration:.1s}.fiad-action:first-child{-webkit-animation-duration:.25s;animation-duration:.25s}.fiad-action>i{font-size:14px;color:#fff}.fiad-action:hover{background-color:#f7b900}.list-actions{position:fixed;-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0);opacity:0;filter:alpha(opacity=0);-webkit-transition:all;transition:all;-webkit-transition-duration:.2s;transition-duration:.2s;padding:20px 70px 20px 25px;top:0;left:0;width:100%;background-color:#2298f7;z-index:20;box-shadow:0 0 10px rgba(0,0,0,.3);text-align:center}.list-actions.list-actions-toggled{-webkit-transform:translateZ(0);transform:translateZ(0);opacity:1;filter:alpha(opacity=100)}.la-close{position:absolute;right:20px;top:0;color:#fff;width:30px;height:30px;border-radius:50%;text-align:center;line-height:30px!important;background:hsla(0,0%,100%,.1);font-weight:400;bottom:0;margin:auto;cursor:pointer}.la-close:hover{background-color:hsla(0,0%,100%,.2)}.la-label{color:#fff;float:left;padding:4px 0}.la-label .fa{font-size:22px;vertical-align:top;margin-right:10px;margin-top:-1px}.la-actions button{background-color:transparent;border:2px solid hsla(0,0%,100%,.9);color:#fff;border-radius:2px;padding:5px 10px;font-size:13px;-webkit-transition:all;transition:all;-webkit-transition-duration:.3s;transition-duration:.3s;margin-left:10px}.la-actions button:hover{background-color:#fff;color:#2298f7}@-webkit-keyframes fiad-action-anim{0%{-webkit-transform:scale(0);transform:scale(0);opacity:0;filter:alpha(opacity=0);right:-20px}to{-webkit-transform:scale(1);transform:scale(1);opacity:1;filter:alpha(opacity=100);right:0}}@keyframes fiad-action-anim{0%{-webkit-transform:scale(0);transform:scale(0);opacity:0;filter:alpha(opacity=0);right:-20px}to{-webkit-transform:scale(1);transform:scale(1);opacity:1;filter:alpha(opacity=100);right:0}}.file-explorer{background-color:#fff;position:relative;height:100%}.file-explorer.toggled{height:100vh;overflow:hidden}.fe-body{min-height:100vh;overflow:auto}@media (min-width:992px){.fe-body{padding:0 0 40px 320px}}@media (max-width:991px){.fe-body{padding:75px 0 80px}}.feb-actions{position:fixed;bottom:30px;right:30px}.feb-actions .dropdown-menu{min-width:55px;width:55px;text-align:center;background:transparent;box-shadow:none;margin:0}.feb-actions.open .feba-btn{-webkit-transform:scale(1);transform:scale(1)}.feb-actions.open .feba-btn:first-child{-webkit-animation-name:feba-btn-anim;animation-name:feba-btn-anim;-webkit-animation-duration:.3s;animation-duration:.3s}.feb-actions.open .feba-btn:last-child{-webkit-animation-name:feba-btn-anim;animation-name:feba-btn-anim;-webkit-animation-duration:.1s;animation-duration:.1s}.feb-actions.open .feba-toggle{background:#ff403c}.feb-actions.open .feba-toggle>span{-webkit-transform:rotate(135deg);transform:rotate(135deg)}.feba-toggle{width:55px;height:55px;line-height:55px;border-radius:50%;background:#ff726f;box-shadow:0 2px 3px rgba(0,0,0,.15);display:inline-block;text-align:center;border:0;padding:0}.feba-toggle span{display:inline-block;height:100%;width:100%}.feba-toggle i{color:#fff;font-size:17px;line-height:58px}.feba-toggle,.feba-toggle>span{-webkit-transition:all;transition:all;-webkit-transition-duration:.25s;transition-duration:.25s;-webkit-backface-visibility:hidden;backface-visibility:hidden}.feba-btn{width:40px;margin-top:10px;height:40px;border-radius:50%;text-align:center;display:inline-block;line-height:40px;box-shadow:0 2px 3px rgba(0,0,0,.15);-webkit-transform:scale(0);transform:scale(0);position:relative}.feba-btn,.feba-btn:focus,.feba-btn:hover{color:#fff}.feba-btn label{width:100%;height:100%;position:absolute;left:0;top:0;cursor:pointer}.feba-bucket{background:#ffc155}.feba-upload{background:#ffc107}@-webkit-keyframes feba-btn-anim{0%{-webkit-transform:scale(0);transform:scale(0);opacity:0;filter:alpha(opacity=0)}to{-webkit-transform:scale(1);transform:scale(1);opacity:1;filter:alpha(opacity=100)}}@keyframes feba-btn-anim{0%{-webkit-transform:scale(0);transform:scale(0);opacity:0;filter:alpha(opacity=0)}to{-webkit-transform:scale(1);transform:scale(1);opacity:1;filter:alpha(opacity=100)}}.ie-warning{background-color:#ff5252;width:100%;height:100%;position:fixed;left:0;top:0;text-align:center}.ie-warning:before{width:1px;content:"";height:100%}.ie-warning .iw-inner,.ie-warning:before{display:inline-block;vertical-align:middle}.iw-inner{width:470px;height:300px;background-color:#fff;border-radius:5px;padding:40px;position:relative}.iw-inner ul{list-style:none;padding:0;margin:0;width:230px;margin-left:80px;margin-top:16px}.iw-inner ul>li{float:left}.iw-inner ul>li>a{display:block;padding:10px 15px 7px;font-size:14px;margin:0 1px;border-radius:3px}.iw-inner ul>li>a:hover{background:#eee}.iw-inner ul>li>a img{height:40px;margin-bottom:5px}.iwi-icon{color:#ff5252;font-size:40px;display:block;line-height:100%;margin-bottom:15px}.iwi-skip{position:absolute;left:0;bottom:-35px;width:100%;color:hsla(0,0%,100%,.6);cursor:pointer}.iwi-skip:hover{color:#fff}.dropdown-menu{padding:15px 0;top:0;margin-top:-1px}.dropdown-menu>li>a{padding:8px 20px;font-size:15px}.dropdown-menu>li>a>i{width:20px;position:relative;top:1px}.dropdown-menu-right>li>a{text-align:right}.alert{border:0;position:fixed;max-width:500px;margin:0;box-shadow:0 4px 5px rgba(0,0,0,.1);color:#fff;width:100%;right:20px;border-radius:3px;padding:17px 50px 17px 17px;z-index:10010;-webkit-animation-duration:.8s;animation-duration:.8s;-webkit-animation-fill-mode:both;animation-fill-mode:both}.alert:not(.progress){top:20px}@media (min-width:768px){.alert:not(.progress){left:50%;margin-left:-250px}}.alert.progress{bottom:20px;right:20px}.alert.alert-danger{background:#ff726f}.alert.alert-success{background:#33d46f}.alert.alert-info{background:#50b2ff}@media (max-width:767px){.alert{left:20px;width:calc(100% - 40px);max-width:100%}}.alert .progress{margin:10px 10px 8px 0;height:5px;box-shadow:none;border-radius:1px;background-color:#50b2ff;border-radius:2px;overflow:hidden}.alert .progress-bar{box-shadow:none;background-color:#fff;height:100%}.alert .close{position:absolute;top:15px}@media (min-width:768px){.modal{text-align:center}.modal:before{content:"";height:100%;width:1px}.modal .modal-dialog,.modal:before{display:inline-block;vertical-align:middle}.modal .modal-dialog{text-align:left;margin:10px auto}}.modal-dark .modal-header{color:hsla(0,0%,100%,.4)}.modal-dark .modal-header small{color:hsla(0,0%,100%,.2)}.modal-dark .modal-content{background-color:#32393f}.modal-backdrop{-webkit-animation-name:fadeIn;animation-name:fadeIn;-webkit-animation-fill-mode:both;animation-fill-mode:both}.modal-backdrop,.modal-dialog{-webkit-animation-duration:.2s;animation-duration:.2s}.modal-dialog{-webkit-animation-name:zoomIn;animation-name:zoomIn;-webkit-animation-fill-mode:both;animation-fill-mode:both}.modal-header{color:#333;position:relative}.modal-header small{display:block;text-transform:none;font-size:12px;margin-top:5px;color:#a8a8a8}.modal-content{border-radius:3px;box-shadow:none}.modal-footer{padding:0 30px 30px}.modal-confirm .modal-dialog,.modal-footer{text-align:center}.mc-icon{margin:0 0 10px}.mc-icon>i{font-size:60px}.mci-red{color:#ff8f8f}.mci-amber{color:#ffc107}.mci-green{color:#64e096}.mc-text{color:#333}.mc-sub{color:#bdbdbd;margin-top:5px;font-size:13px}@media (max-width:767px){.modal-about{text-align:center}.modal-about .modal-dialog{max-width:400px;width:90%;margin:20px auto 0}}.ma-inner{display:flex;flex-direction:row;align-items:center;min-height:350px;position:relative}@media (min-width:768px){.ma-inner:before{content:"";width:150px;height:100%;top:0;left:0;position:absolute;border-radius:3px 0 0 3px;background-color:#23282c}}.mai-item:first-child{width:150px;text-align:center}.mai-item:last-child{flex:4;padding:30px}.maii-logo{width:70px;position:relative}.maii-list{list-style:none;padding:0}.maii-list>li{margin-bottom:15px}.maii-list>li div{color:hsla(0,0%,100%,.8);text-transform:uppercase;font-size:14px}.maii-list>li small{font-size:13px;color:hsla(0,0%,100%,.4)}.toggle-password{position:absolute;bottom:30px;right:35px;width:30px;height:30px;border:1px solid #eee;border-radius:0;text-align:center;cursor:pointer;z-index:10;background-color:#fff;padding-top:5px}.toggle-password.toggled{background:#eee}.pm-body{padding-bottom:30px}.pmb-header{margin-bottom:35px}.pmb-list{display:flex;flex-flow:row nowrap;align-items:center;justify-content:center;padding:10px 35px}.pmb-list:nth-child(2n){background-color:#f7f7f7}.pmb-list .form-control{padding-left:0;padding-right:0}header.pmb-list{margin:20px 0 10px}.pmbl-item{display:block;font-size:13px}.pmbl-item:first-child{flex:2}.pmbl-item:nth-child(2){margin:0 25px;width:150px}.pmbl-item:nth-child(3){width:70px}div.pmb-list select{border:0}div.pmb-list .pml-item:not(:last-child){padding:0 5px}.modal-create-bucket .modal-dialog{position:fixed;right:25px;bottom:95px;margin:0;height:110px}.modal-create-bucket .modal-content{width:100%;height:100%}',""]); +},function(A,M,t){function I(A){return null===A||void 0===A}function g(A){return!(!A||"object"!=typeof A||"number"!=typeof A.length)&&("function"==typeof A.copy&&"function"==typeof A.slice&&!(A.length>0&&"number"!=typeof A[0]))}function e(A,M,t){var e,o;if(I(A)||I(M))return!1;if(A.prototype!==M.prototype)return!1;if(E(A))return!!E(M)&&(A=i.call(A),M=i.call(M),N(A,M,t));if(g(A)){if(!g(M))return!1;if(A.length!==M.length)return!1;for(e=0;e=0;e--)if(n[e]!=C[e])return!1;for(e=n.length-1;e>=0;e--)if(o=n[e],!N(A[o],M[o],t))return!1;return typeof A==typeof M}var i=Array.prototype.slice,T=t(279),E=t(278),N=A.exports=function(A,M,t){return t||(t={}),A===M||(A instanceof Date&&M instanceof Date?A.getTime()===M.getTime():!A||!M||"object"!=typeof A&&"object"!=typeof M?t.strict?A===M:A==M:e(A,M,t))}},function(A,M){function t(A){return"[object Arguments]"==Object.prototype.toString.call(A)}function I(A){return A&&"object"==typeof A&&"number"==typeof A.length&&Object.prototype.hasOwnProperty.call(A,"callee")&&!Object.prototype.propertyIsEnumerable.call(A,"callee")||!1}var g="[object Arguments]"==function(){return Object.prototype.toString.call(arguments)}();M=A.exports=g?t:I,M.supported=t,M.unsupported=I},function(A,M){function t(A){var M=[];for(var t in A)M.push(t);return M}M=A.exports="function"==typeof Object.keys?Object.keys:t,M.shim=t},function(A,M,t){"use strict";var I=t(139);A.exports=function(A,M){A.classList?A.classList.add(M):I(A)||(A.className=A.className+" "+M)}},function(A,M,t){"use strict";A.exports={addClass:t(280),removeClass:t(282),hasClass:t(139)}},function(A,M){"use strict";A.exports=function(A,M){A.classList?A.classList.remove(M):A.className=A.className.replace(new RegExp("(^|\\s)"+M+"(?:\\s|$)","g"),"$1").replace(/\s+/g," ").replace(/^\s*|\s*$/g,"")}},function(A,M,t){"use strict";var I=t(40),g=t(287);A.exports=function(A,M){return function(t){var e=t.currentTarget,i=t.target,T=g(e,A);T.some(function(A){return I(A,i)})&&M.call(this,t)}}},function(A,M,t){"use strict";var I=t(81),g=t(140),e=t(283);A.exports={on:I,off:g,filter:e}},function(A,M,t){"use strict";function I(A){return A.nodeName&&A.nodeName.toLowerCase()}function g(A){for(var M=(0,T.default)(A),t=A&&A.offsetParent;t&&"html"!==I(A)&&"static"===(0,N.default)(t,"position");)t=t.offsetParent;return t||M.documentElement}var e=t(58);M.__esModule=!0,M.default=g;var i=t(39),T=e.interopRequireDefault(i),E=t(82),N=e.interopRequireDefault(E);A.exports=M.default},function(A,M,t){"use strict";function I(A){return A.nodeName&&A.nodeName.toLowerCase()}function g(A,M){var t,g={top:0,left:0};return"fixed"===(0,a.default)(A,"position")?t=A.getBoundingClientRect():(M=M||(0,N.default)(A),t=(0,T.default)(A),"html"!==I(M)&&(g=(0,T.default)(M)),g.top+=parseInt((0,a.default)(M,"borderTopWidth"),10)-(0,n.default)(M)||0,g.left+=parseInt((0,a.default)(M,"borderLeftWidth"),10)-(0,c.default)(M)||0),e._extends({},t,{top:t.top-g.top-(parseInt((0,a.default)(A,"marginTop"),10)||0),left:t.left-g.left-(parseInt((0,a.default)(A,"marginLeft"),10)||0)})}var e=t(58);M.__esModule=!0,M.default=g;var i=t(141),T=e.interopRequireDefault(i),E=t(285),N=e.interopRequireDefault(E),o=t(142),n=e.interopRequireDefault(o),C=t(288),c=e.interopRequireDefault(C),D=t(82),a=e.interopRequireDefault(D);A.exports=M.default},function(A,M){"use strict";var t=/^[\w-]*$/,I=Function.prototype.bind.call(Function.prototype.call,[].slice);A.exports=function(A,M){var g,e="#"===M[0],i="."===M[0],T=e||i?M.slice(1):M,E=t.test(T);return E?e?(A=A.getElementById?A:document,(g=A.getElementById(T))?[g]:[]):I(A.getElementsByClassName&&i?A.getElementsByClassName(T):A.getElementsByTagName(M)):I(A.querySelectorAll(M))}},function(A,M,t){"use strict";var I=t(57);A.exports=function(A,M){var t=I(A);return void 0===M?t?"pageXOffset"in t?t.pageXOffset:t.document.documentElement.scrollLeft:A.scrollLeft:void(t?t.scrollTo(M,"pageYOffset"in t?t.pageYOffset:t.document.documentElement.scrollTop):A.scrollLeft=M)}},function(A,M,t){"use strict";var I=t(58),g=t(143),e=I.interopRequireDefault(g),i=/^(top|right|bottom|left)$/,T=/^([+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|))(?!px)[a-z%]+$/i;A.exports=function(A){if(!A)throw new TypeError("No Element passed to ` + "`" + `getComputedStyle()` + "`" + `");var M=A.ownerDocument;return"defaultView"in M?M.defaultView.opener?A.ownerDocument.defaultView.getComputedStyle(A,null):window.getComputedStyle(A,null):{getPropertyValue:function(M){var t=A.style;M=(0,e.default)(M),"float"==M&&(M="styleFloat");var I=A.currentStyle[M]||null;if(null==I&&t&&t[M]&&(I=t[M]),T.test(I)&&!i.test(M)){var g=t.left,E=A.runtimeStyle,N=E&&E.left;N&&(E.left=A.currentStyle.left),t.left="fontSize"===M?"1em":I,I=t.pixelLeft+"px",t.left=g,N&&(E.left=N)}return I}}}},function(A,M){"use strict";A.exports=function(A,M){return"removeProperty"in A.style?A.style.removeProperty(M):A.style.removeAttribute(M)}},function(A,M,t){"use strict";function I(){var A,M="",t={O:"otransitionend",Moz:"transitionend",Webkit:"webkitTransitionEnd",ms:"MSTransitionEnd"},I=document.createElement("div");for(var g in t)if(N.call(t,g)&&void 0!==I.style[g+"TransitionProperty"]){M="-"+g.toLowerCase()+"-",A=t[g];break}return A||void 0===I.style.transitionProperty||(A="transitionend"),{end:A,prefix:M}}var g,e,i,T,E=t(32),N=Object.prototype.hasOwnProperty,o="transform",n={};E&&(n=I(),o=n.prefix+o,i=n.prefix+"transition-property",e=n.prefix+"transition-duration",T=n.prefix+"transition-delay",g=n.prefix+"transition-timing-function"),A.exports={transform:o,end:n.end,property:i,timing:g,delay:T,duration:e}},function(A,M){"use strict";var t=/-(.)/g;A.exports=function(A){return A.replace(t,function(A,M){return M.toUpperCase()})}},function(A,M){"use strict";var t=/([A-Z])/g;A.exports=function(A){return A.replace(t,"-$1").toLowerCase()}},function(A,M,t){"use strict";var I=t(293),g=/^ms-/;A.exports=function(A){return I(A).replace(g,"-ms-")}},function(A,M){"use strict";function t(A){return A.replace(I,function(A,M){return M.toUpperCase()})}var I=/-(.)/g;A.exports=t},function(A,M,t){"use strict";function I(A){return g(A.replace(e,"ms-"))}var g=t(295),e=/^-ms-/;A.exports=I},function(A,M,t){"use strict";function I(A){return!!A&&("object"==typeof A||"function"==typeof A)&&"length"in A&&!("setInterval"in A)&&"number"!=typeof A.nodeType&&(Array.isArray(A)||"callee"in A||"item"in A)}function g(A){return I(A)?Array.isArray(A)?A.slice():e(A):[A]}var e=t(306);A.exports=g},function(A,M,t){"use strict";function I(A){var M=A.match(o);return M&&M[1].toLowerCase()}function g(A,M){var t=N;N?void 0:E(!1);var g=I(A),e=g&&T(g);if(e){t.innerHTML=e[1]+A+e[2];for(var o=e[0];o--;)t=t.lastChild}else t.innerHTML=A;var n=t.getElementsByTagName("script");n.length&&(M?void 0:E(!1),i(n).forEach(M));for(var C=i(t.childNodes);t.lastChild;)t.removeChild(t.lastChild);return C}var e=t(10),i=t(297),T=t(149),E=t(2),N=e.canUseDOM?document.createElement("div"):null,o=/^\s*<(\w+)/;A.exports=g},function(A,M){"use strict";function t(A){return A===window?{x:window.pageXOffset||document.documentElement.scrollLeft,y:window.pageYOffset||document.documentElement.scrollTop}:{x:A.scrollLeft,y:A.scrollTop}}A.exports=t},function(A,M){"use strict";function t(A){return A.replace(I,"-$1").toLowerCase()}var I=/([A-Z])/g;A.exports=t},function(A,M,t){"use strict";function I(A){return g(A).replace(e,"-ms-")}var g=t(300),e=/^ms-/;A.exports=I},function(A,M){"use strict";function t(A){return!(!A||!("function"==typeof Node?A instanceof Node:"object"==typeof A&&"number"==typeof A.nodeType&&"string"==typeof A.nodeName))}A.exports=t},function(A,M,t){"use strict";function I(A){return g(A)&&3==A.nodeType}var g=t(302);A.exports=I},function(A,M){"use strict";function t(A,M,t){if(!A)return null;var g={};for(var e in A)I.call(A,e)&&(g[e]=M.call(t,A[e],e,A));return g}var I=Object.prototype.hasOwnProperty;A.exports=t},function(A,M){"use strict";function t(A){var M={};return function(t){return M.hasOwnProperty(t)||(M[t]=A.call(this,t)),M[t]}}A.exports=t},function(A,M,t){"use strict";function I(A){var M=A.length;if(Array.isArray(A)||"object"!=typeof A&&"function"!=typeof A?g(!1):void 0,"number"!=typeof M?g(!1):void 0,0===M||M-1 in A?void 0:g(!1),A.hasOwnProperty)try{return Array.prototype.slice.call(A)}catch(A){}for(var t=Array(M),I=0;I=T?i(M):null,C=M.length;n&&(N=e,o=!1,M=n);A:for(;++Eg?0:g+M),t=void 0===t||t>g?g:+t||0,t<0&&(t+=g),g=M>t?0:t-M>>>0,M>>>=0;for(var e=Array(g);++I-1?t[N]:void 0}return e(t,I,A)}}var g=t(319),e=t(322),i=t(323),T=t(28);A.exports=I},function(A,M,t){function I(A,M,t,I,e,i,T){var E=-1,N=A.length,o=M.length;if(N!=o&&!(e&&o>N))return!1;for(;++E=T?i(M):null,C=M.length;n&&(N=e,o=!1,M=n);A:for(;++Eg?0:g+M),t=void 0===t||t>g?g:+t||0,t<0&&(t+=g),g=M>t?0:t-M>>>0,M>>>=0;for(var e=Array(g);++I-1?t[N]:void 0}return e(t,I,A)}}var g=t(318),e=t(321),i=t(322),T=t(28);A.exports=I},function(A,M,t){function I(A,M,t,I,e,i,T){var E=-1,N=A.length,o=M.length;if(N!=o&&!(e&&o>N))return!1;for(;++Eo||N===o&&"application/"===M[T].substr(0,12))continue}M[T]=I}}})}var E=t(351),N=t(354).extname,o=/^\s*([^;\s]*)(?:;|\s|$)/,n=/^text\//i;M.charset=I,M.charsets={lookup:I},M.contentType=g,M.extension=e,M.extensions=Object.create(null),M.lookup=i,M.types=Object.create(null),T(M.extensions,M.types)},function(A,M){function t(A){throw new Error("Cannot find module '"+A+"'.")}t.keys=function(){return[]},t.resolve=t,A.exports=t,t.id=353},function(A,M,t){(function(A){function t(A,M){for(var t=0,I=A.length-1;I>=0;I--){var g=A[I];"."===g?A.splice(I,1):".."===g?(A.splice(I,1),t++):t&&(A.splice(I,1),t--)}if(M)for(;t--;t)A.unshift("..");return A}function I(A,M){if(A.filter)return A.filter(M);for(var t=[],I=0;I=-1&&!g;e--){var i=e>=0?arguments[e]:A.cwd();if("string"!=typeof i)throw new TypeError("Arguments to path.resolve must be strings");i&&(M=i+"/"+M,g="/"===i.charAt(0))}return M=t(I(M.split("/"),function(A){return!!A}),!g).join("/"),(g?"/":"")+M||"."},M.normalize=function(A){var g=M.isAbsolute(A),e="/"===i(A,-1);return A=t(I(A.split("/"),function(A){return!!A}),!g).join("/"),A||g||(A="."),A&&e&&(A+="/"),(g?"/":"")+A},M.isAbsolute=function(A){return"/"===A.charAt(0)},M.join=function(){var A=Array.prototype.slice.call(arguments,0);return M.normalize(I(A,function(A,M){if("string"!=typeof A)throw new TypeError("Arguments to path.join must be strings");return A}).join("/"))},M.relative=function(A,t){function I(A){for(var M=0;M=0&&""===A[t];t--);return M>t?[]:A.slice(M,t-M+1)}A=M.resolve(A).substr(1),t=M.resolve(t).substr(1);for(var g=I(A.split("/")),e=I(t.split("/")),i=Math.min(g.length,e.length),T=i,E=0;E=0;e--){var i=I[e]+g;if(i in M)return i}return!1}},function(A,M,t){"use strict";var I=t(497);M.extract=function(A){return A.split("?")[1]||""},M.parse=function(A){return"string"!=typeof A?{}:(A=A.trim().replace(/^(\?|#|&)/,""),A?A.split("&").reduce(function(A,M){var t=M.replace(/\+/g," ").split("="),I=t.shift(),g=t.length>0?t.join("="):void 0;return I=decodeURIComponent(I),g=void 0===g?null:decodeURIComponent(g),A.hasOwnProperty(I)?Array.isArray(A[I])?A[I].push(g):A[I]=[A[I],g]:A[I]=g,A},{}):{})},M.stringify=function(A){return A?Object.keys(A).sort().map(function(M){var t=A[M];return void 0===t?"":null===t?M:Array.isArray(t)?t.slice().sort().map(function(A){return I(M)+"="+I(A)}).join("&"):I(M)+"="+I(t)}).filter(function(A){return A.length>0}).join("&"):""}},function(A,M){"use strict";function t(A,M){return Object.prototype.hasOwnProperty.call(A,M)}A.exports=function(A,M,I,g){M=M||"&",I=I||"=";var e={};if("string"!=typeof A||0===A.length)return e;var i=/\+/g;A=A.split(M);var T=1e3;g&&"number"==typeof g.maxKeys&&(T=g.maxKeys);var E=A.length;T>0&&E>T&&(E=T);for(var N=0;N=0?(o=D.substr(0,a),n=D.substr(a+1)):(o=D,n=""),C=decodeURIComponent(o),c=decodeURIComponent(n),t(e,C)?Array.isArray(e[C])?e[C].push(c):e[C]=[e[C],c]:e[C]=c}return e}},function(A,M){"use strict";var t=function(A){switch(typeof A){case"string":return A;case"boolean":return A?"true":"false";case"number":return isFinite(A)?A:"";default:return""}};A.exports=function(A,M,I,g){return M=M||"&",I=I||"=",null===A&&(A=void 0),"object"==typeof A?Object.keys(A).map(function(g){var e=encodeURIComponent(t(g))+I;return Array.isArray(A[g])?A[g].map(function(A){return e+encodeURIComponent(t(A))}).join(M):e+encodeURIComponent(t(A[g]))}).join(M):g?encodeURIComponent(t(g))+I+encodeURIComponent(t(A)):""}},function(A,M,t){"use strict";M.decode=M.parse=t(358),M.encode=M.stringify=t(359)},function(A,M,t){(function(M){for(var I=t(355),g="undefined"==typeof window?M:window,e=["moz","webkit"],i="AnimationFrame",T=g["request"+i],E=g["cancel"+i]||g["cancelRequest"+i],N=0;!T&&N1)||(e=A,!1)}),e)return new Error("(children) "+I+" - Duplicate children detected of bsRole: "+e+". Only one child each allowed with the following bsRoles: "+M.join(", "))})}},A.exports=M.default},function(A,M,t){"use strict";function I(A){var M=[];return void 0===A?M:(i.default.forEach(A,function(A){M.push(A)}),M)}var g=t(6).default;M.__esModule=!0,M.default=I;var e=t(51),i=g(e);A.exports=M.default},function(A,M,t){"use strict";function I(A){return A&&A.__esModule?A:{default:A}}function g(A,M){var t={};for(var I in A)M.indexOf(I)>=0||Object.prototype.hasOwnProperty.call(A,I)&&(t[I]=A[I]);return t}Object.defineProperty(M,"__esModule",{value:!0}),M.CopyToClipboard=void 0;var e=Object.assign||function(A){for(var M=1;M=0||Object.prototype.hasOwnProperty.call(A,I)&&(t[I]=A[I]);return t}function e(A){var M=A.style,t=g(A,["style"]),I=o({},M,{height:6,right:2,bottom:2,left:2,borderRadius:3});return C.default.createElement("div",o({style:I},t))}function i(A){var M=A.style,t=g(A,["style"]),I=o({},M,{width:6,right:2,bottom:2,top:2,borderRadius:3});return C.default.createElement("div",o({style:I},t))}function T(A){var M=A.style,t=g(A,["style"]),I=o({},M,{cursor:"pointer",borderRadius:"inherit",backgroundColor:"rgba(0,0,0,.2)"});return C.default.createElement("div",o({style:I},t))}function E(A){var M=A.style,t=g(A,["style"]),I=o({},M,{cursor:"pointer",borderRadius:"inherit",backgroundColor:"rgba(0,0,0,.2)"});return C.default.createElement("div",o({style:I},t))}function N(A){return C.default.createElement("div",A)}var o=Object.assign||function(A){for(var M=1;M=0||Object.prototype.hasOwnProperty.call(A,I)&&(t[I]=A[I]);return t}var e=Object.assign||function(A){for(var M=1;M=0||Object.prototype.hasOwnProperty.call(A,I)&&(t[I]=A[I]);return t}function e(A,M){if(!(A instanceof M))throw new TypeError("Cannot call a class as a function")}function i(A,M){if(!A)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!M||"object"!=typeof M&&"function"!=typeof M?A:M}function T(A,M){if("function"!=typeof M&&null!==M)throw new TypeError("Super expression must either be null or a function, not "+typeof M);A.prototype=Object.create(M&&M.prototype,{constructor:{value:A,enumerable:!1,writable:!0,configurable:!0}}),M&&(Object.setPrototypeOf?Object.setPrototypeOf(A,M):A.__proto__=M)}Object.defineProperty(M,"__esModule",{value:!0});var E=Object.assign||function(A){for(var M=1;M0||(this.setState({isDragActive:!1,isDragReject:!1}),this.props.onDragLeave&&this.props.onDragLeave.call(this,A))}},{key:"onDrop",value:function A(M){var t=this,I=this.props,A=I.onDrop,g=I.onDropAccepted,e=I.onDropRejected,i=I.multiple,T=I.disablePreview,E=(0,a.default)(M,i),N=[],o=[];M.preventDefault(),this.enterCounter=0,this.isFileDialogActive=!1,E.forEach(function(A){T||(A.preview=window.URL.createObjectURL(A)),t.fileAccepted(A)&&t.fileMatchSize(A)?N.push(A):o.push(A)}),A&&A.call(this,N,o,M),o.length>0&&e&&e.call(this,o,M),N.length>0&&g&&g.call(this,N,M),this.setState({isDragActive:!1,isDragReject:!1})}},{key:"onClick",value:function A(M){M.stopPropagation();var t=this.props,A=t.onClick,I=t.disableClick;I||(this.open(),A&&A.call(this,M))}},{key:"onFileDialogCancel",value:function A(){var A=this.props.onFileDialogCancel,M=this.fileInputEl,t=this.isFileDialogActive;A&&t&&setTimeout(function(){var I=M.files;I.length||(t=!1,A())},300)}},{key:"fileAccepted",value:function(A){return(0,c.default)(A,this.props.accept)}},{key:"fileMatchSize",value:function(A){return A.size<=this.props.maxSize&&A.size>=this.props.minSize}},{key:"allFilesAccepted",value:function(A){return A.every(this.fileAccepted)}},{key:"open",value:function(){this.isFileDialogActive=!0,this.fileInputEl.value=null,this.fileInputEl.click()}},{key:"render",value:function(){var A=this,M=this.props,t=M.accept,I=M.activeClassName,e=M.inputProps,i=M.multiple,T=M.name,N=M.rejectClassName,o=g(M,["accept","activeClassName","inputProps","multiple","name","rejectClassName"]),C=o.activeStyle,c=o.className,D=o.rejectStyle,a=o.style,Q=g(o,["activeStyle","className","rejectStyle","style"]),r=this.state,s=r.isDragActive,x=r.isDragReject;c=c||"",s&&I&&(c+=" "+I),x&&N&&(c+=" "+N),c||a||C||D||(a={width:200,height:200,borderWidth:2,borderColor:"#666",borderStyle:"dashed",borderRadius:5},C={borderStyle:"solid",backgroundColor:"#eee"},D={borderStyle:"solid",backgroundColor:"#ffdddd"});var j=void 0;j=C&&s?E({},a,C):D&&x?E({},a,D):E({},a);var y={accept:t,type:"file",style:{display:"none"},multiple:B&&i,ref:function(M){return A.fileInputEl=M},onChange:this.onDrop};T&&T.length&&(y.name=T);var u=["acceptedFiles","disablePreview","disableClick","onDropAccepted","onDropRejected","onFileDialogCancel","maxSize","minSize"],w=E({},Q);return u.forEach(function(A){return delete w[A]}),n.default.createElement("div",E({className:c,style:j},w,{onClick:this.onClick,onDragStart:this.onDragStart,onDragEnter:this.onDragEnter,onDragOver:this.onDragOver,onDragLeave:this.onDragLeave,onDrop:this.onDrop}),this.props.children,n.default.createElement("input",E({},e,y)))}}]),M}(n.default.Component);Q.defaultProps={disablePreview:!1,disableClick:!1,multiple:!0,maxSize:1/0,minSize:0},Q.propTypes={onClick:n.default.PropTypes.func,onDrop:n.default.PropTypes.func,onDropAccepted:n.default.PropTypes.func,onDropRejected:n.default.PropTypes.func,onDragStart:n.default.PropTypes.func,onDragEnter:n.default.PropTypes.func,onDragOver:n.default.PropTypes.func,onDragLeave:n.default.PropTypes.func,children:n.default.PropTypes.node,style:n.default.PropTypes.object,activeStyle:n.default.PropTypes.object,rejectStyle:n.default.PropTypes.object,className:n.default.PropTypes.string,activeClassName:n.default.PropTypes.string,rejectClassName:n.default.PropTypes.string,disablePreview:n.default.PropTypes.bool,disableClick:n.default.PropTypes.bool,onFileDialogCancel:n.default.PropTypes.func,inputProps:n.default.PropTypes.object,multiple:n.default.PropTypes.bool,accept:n.default.PropTypes.string,name:n.default.PropTypes.string,maxSize:n.default.PropTypes.number,minSize:n.default.PropTypes.number},M.default=Q,A.exports=M.default},function(M,t){M.exports=A},function(A,M){A.exports=function(A){function M(I){if(t[I])return t[I].exports;var g=t[I]={exports:{},id:I,loaded:!1};return A[I].call(g.exports,g,g.exports,M),g.loaded=!0,g.exports}var t={};return M.m=A,M.c=t,M.p="",M(0)}([function(A,M,t){"use strict";M.__esModule=!0,t(8),t(9),M.default=function(A,M){if(A&&M){var t=function(){var t=Array.isArray(M)?M:M.split(","),I=A.name||"",g=A.type||"",e=g.replace(/\/.*$/,"");return{v:t.some(function(A){var M=A.trim();return"."===M.charAt(0)?I.toLowerCase().endsWith(M.toLowerCase()):/\/\*$/.test(M)?e===M.replace(/\/.*$/,""):g===M})}}();if("object"==typeof t)return t.v}return!0},A.exports=M.default},function(A,M){var t=A.exports={version:"1.2.2"};"number"==typeof __e&&(__e=t)},function(A,M){var t=A.exports="undefined"!=typeof window&&window.Math==Math?window:"undefined"!=typeof self&&self.Math==Math?self:Function("return this")();"number"==typeof __g&&(__g=t)},function(A,M,t){var I=t(2),g=t(1),e=t(4),i=t(19),T="prototype",E=function(A,M){return function(){return A.apply(M,arguments)}},N=function(A,M,t){var o,n,C,c,D=A&N.G,a=A&N.P,B=D?I:A&N.S?I[M]||(I[M]={}):(I[M]||{})[T],Q=D?g:g[M]||(g[M]={});D&&(t=M);for(o in t)n=!(A&N.F)&&B&&o in B,C=(n?B:t)[o],c=A&N.B&&n?E(C,I):a&&"function"==typeof C?E(Function.call,C):C,B&&!n&&i(B,o,C),Q[o]!=C&&e(Q,o,c),a&&((Q[T]||(Q[T]={}))[o]=C)};I.core=g,N.F=1,N.G=2,N.S=4,N.P=8,N.B=16,N.W=32,A.exports=N},function(A,M,t){var I=t(5),g=t(18);A.exports=t(22)?function(A,M,t){return I.setDesc(A,M,g(1,t))}:function(A,M,t){return A[M]=t,A}},function(A,M){var t=Object;A.exports={create:t.create,getProto:t.getPrototypeOf,isEnum:{}.propertyIsEnumerable,getDesc:t.getOwnPropertyDescriptor,setDesc:t.defineProperty,setDescs:t.defineProperties,getKeys:t.keys,getNames:t.getOwnPropertyNames,getSymbols:t.getOwnPropertySymbols,each:[].forEach}},function(A,M){var t=0,I=Math.random();A.exports=function(A){return"Symbol(".concat(void 0===A?"":A,")_",(++t+I).toString(36))}},function(A,M,t){var I=t(20)("wks"),g=t(2).Symbol;A.exports=function(A){return I[A]||(I[A]=g&&g[A]||(g||t(6))("Symbol."+A))}},function(A,M,t){t(26),A.exports=t(1).Array.some},function(A,M,t){t(25),A.exports=t(1).String.endsWith},function(A,M){A.exports=function(A){if("function"!=typeof A)throw TypeError(A+" is not a function!");return A}},function(A,M){var t={}.toString;A.exports=function(A){return t.call(A).slice(8,-1)}},function(A,M,t){var I=t(10);A.exports=function(A,M,t){if(I(A),void 0===M)return A;switch(t){case 1:return function(t){return A.call(M,t)};case 2:return function(t,I){return A.call(M,t,I)};case 3:return function(t,I,g){return A.call(M,t,I,g)}}return function(){return A.apply(M,arguments)}}},function(A,M){A.exports=function(A){if(void 0==A)throw TypeError("Can't call method on "+A);return A}},function(A,M,t){A.exports=function(A){var M=/./;try{"/./"[A](M)}catch(I){try{return M[t(7)("match")]=!1,!"/./"[A](M)}catch(A){}}return!0}},function(A,M){A.exports=function(A){try{return!!A()}catch(A){return!0}}},function(A,M){A.exports=function(A){return"object"==typeof A?null!==A:"function"==typeof A}},function(A,M,t){var I=t(16),g=t(11),e=t(7)("match");A.exports=function(A){var M;return I(A)&&(void 0!==(M=A[e])?!!M:"RegExp"==g(A))}},function(A,M){A.exports=function(A,M){return{enumerable:!(1&A),configurable:!(2&A),writable:!(4&A),value:M}}},function(A,M,t){var I=t(2),g=t(4),e=t(6)("src"),i="toString",T=Function[i],E=(""+T).split(i);t(1).inspectSource=function(A){return T.call(A)},(A.exports=function(A,M,t,i){"function"==typeof t&&(g(t,e,A[M]?""+A[M]:E.join(String(M))),"name"in t||(t.name=M)),A===I?A[M]=t:(i||delete A[M],g(A,M,t))})(Function.prototype,i,function(){return"function"==typeof this&&this[e]||T.call(this)})},function(A,M,t){var I=t(2),g="__core-js_shared__",e=I[g]||(I[g]={});A.exports=function(A){return e[A]||(e[A]={})}},function(A,M,t){var I=t(17),g=t(13);A.exports=function(A,M,t){if(I(M))throw TypeError("String#"+t+" doesn't accept regex!");return String(g(A))}},function(A,M,t){A.exports=!t(15)(function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a})},function(A,M){var t=Math.ceil,I=Math.floor;A.exports=function(A){return isNaN(A=+A)?0:(A>0?I:t)(A)}},function(A,M,t){var I=t(23),g=Math.min;A.exports=function(A){return A>0?g(I(A),9007199254740991):0}},function(A,M,t){"use strict";var I=t(3),g=t(24),e=t(21),i="endsWith",T=""[i];I(I.P+I.F*t(14)(i),"String",{endsWith:function(A){var M=e(this,A,i),t=arguments,I=t.length>1?t[1]:void 0,E=g(M.length),N=void 0===I?E:Math.min(g(I),E),o=String(A);return T?T.call(M,o,N):M.slice(N-o.length,N)===o}})},function(A,M,t){var I=t(5),g=t(3),e=t(1).Array||Array,i={},T=function(A,M){I.each.call(A.split(","),function(A){void 0==M&&A in e?i[A]=e[A]:A in[]&&(i[A]=t(12)(Function.call,[][A],M))})};T("pop,reverse,shift,keys,values,entries",1),T("indexOf,every,some,forEach,map,filter,find,findIndex,includes",3),T("join,slice,concat,push,splice,unshift,sort,lastIndexOf,reduce,reduceRight,copyWithin,fill"),g(g.S,"Array",i)}])},function(A,M){"use strict";function t(A){var M=!(arguments.length>1&&void 0!==arguments[1])||arguments[1],t=[];if(A.dataTransfer){var I=A.dataTransfer;I.files&&I.files.length?t=I.files:I.items&&I.items.length&&(t=I.items)}else A.target&&A.target.files&&(t=A.target.files);return t.length>0&&(t=M?t:[t[0]]),Array.prototype.slice.call(t)}Object.defineProperty(M,"__esModule",{value:!0}),M.default=t,A.exports=M.default}])})},function(A,M,t){"use strict";function I(A){return A&&A.__esModule?A:{default:A}}function g(A,M){var t={};for(var I in A)M.indexOf(I)>=0||Object.prototype.hasOwnProperty.call(A,I)&&(t[I]=A[I]);return t}function e(A,M){if(!(A instanceof M))throw new TypeError("Cannot call a class as a function")}function i(A,M){if(!A)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!M||"object"!=typeof M&&"function"!=typeof M?A:M}function T(A,M){if("function"!=typeof M&&null!==M)throw new TypeError("Super expression must either be null or a function, not "+typeof M);A.prototype=Object.create(M&&M.prototype,{constructor:{value:A,enumerable:!1,writable:!0,configurable:!0}}),M&&(Object.setPrototypeOf?Object.setPrototypeOf(A,M):A.__proto__=M)}Object.defineProperty(M,"__esModule",{value:!0});var E=function(){function A(A,M){for(var t=0;t0&&void 0!==arguments[0]?arguments[0]:{},t=M.hideSiblingNodes,I=void 0===t||t,e=M.handleContainerOverflow,i=void 0===e||e;g(this,A),this.hideSiblingNodes=I,this.handleContainerOverflow=i,this.modals=[],this.containers=[],this.data=[]}return N(A,[{key:"add",value:function(A,M,t){var I=this.modals.indexOf(A),g=this.containers.indexOf(M);if(I!==-1)return I;if(I=this.modals.length,this.modals.push(A),this.hideSiblingNodes&&(0,r.hideSiblings)(M,A.mountNode),g!==-1)return this.data[g].modals.push(A),I;var e={modals:[A],classes:t?t.split(/\s+/):[],overflowing:(0,Q.default)(M)};return this.handleContainerOverflow&&T(e,M),e.classes.forEach(c.default.addClass.bind(null,M)),this.containers.push(M),this.data.push(e),I}},{key:"remove",value:function(A){var M=this.modals.indexOf(A);if(M!==-1){var t=i(this.data,A),I=this.data[t],g=this.containers[t];I.modals.splice(I.modals.indexOf(A),1),this.modals.splice(M,1),0===I.modals.length?(I.classes.forEach(c.default.removeClass.bind(null,g)),this.handleContainerOverflow&&E(I,g),this.hideSiblingNodes&&(0,r.showSiblings)(g,A.mountNode),this.containers.splice(t,1),this.data.splice(t,1)):this.hideSiblingNodes&&(0,r.ariaHidden)(!1,I.modals[I.modals.length-1].mountNode)}}},{key:"isTopModal",value:function(A){return!!this.modals.length&&this.modals[this.modals.length-1]===A}}]),A}();M.default=s,A.exports=M.default},function(A,M,t){"use strict";function I(A){return A&&A.__esModule?A:{default:A}}function g(A,M){var t={};for(var I in A)M.indexOf(I)>=0||Object.prototype.hasOwnProperty.call(A,I)&&(t[I]=A[I]);return t}function e(A,M){if(!(A instanceof M))throw new TypeError("Cannot call a class as a function")}function i(A,M){if(!A)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!M||"object"!=typeof M&&"function"!=typeof M?A:M}function T(A,M){if("function"!=typeof M&&null!==M)throw new TypeError("Super expression must either be null or a function, not "+typeof M);A.prototype=Object.create(M&&M.prototype,{constructor:{value:A,enumerable:!1,writable:!0,configurable:!0}}),M&&(Object.setPrototypeOf?Object.setPrototypeOf(A,M):A.__proto__=M)}Object.defineProperty(M,"__esModule",{value:!0});var E=Object.assign||function(A){for(var M=1;M1?t-1:0),g=1;g=0||Object.prototype.hasOwnProperty.call(A,I)&&(t[I]=A[I]);return t}function e(A,M){if(!(A instanceof M))throw new TypeError("Cannot call a class as a function")}function i(A,M){if(!A)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!M||"object"!=typeof M&&"function"!=typeof M?A:M}function T(A,M){if("function"!=typeof M&&null!==M)throw new TypeError("Super expression must either be null or a function, not "+typeof M);A.prototype=Object.create(M&&M.prototype,{constructor:{value:A,enumerable:!1,writable:!0,configurable:!0}}),M&&(Object.setPrototypeOf?Object.setPrototypeOf(A,M):A.__proto__=M)}Object.defineProperty(M,"__esModule",{value:!0});var E=Object.assign||function(A){for(var M=1;M=0||Object.prototype.hasOwnProperty.call(A,I)&&(t[I]=A[I]);return t}function e(A,M){if(!(A instanceof M))throw new TypeError("Cannot call a class as a function")}function i(A,M){if(!A)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!M||"object"!=typeof M&&"function"!=typeof M?A:M}function T(A,M){if("function"!=typeof M&&null!==M)throw new TypeError("Super expression must either be null or a function, not "+typeof M);A.prototype=Object.create(M&&M.prototype,{constructor:{value:A,enumerable:!1,writable:!0,configurable:!0}}),M&&(Object.setPrototypeOf?Object.setPrototypeOf(A,M):A.__proto__=M)}function E(){}Object.defineProperty(M,"__esModule",{value:!0}),M.EXITING=M.ENTERED=M.ENTERING=M.EXITED=M.UNMOUNTED=void 0;var N=Object.assign||function(A){for(var M=1;MT?T-N:0}function i(A,M,t,I){var e=g(t),i=e.width,T=A-I,E=A+I+M;return T<0?-T:E>i?i-E:0}function T(A,M,t,I,g){var T="BODY"===I.tagName?(0,N.default)(t):(0,n.default)(t,I),E=(0,N.default)(M),o=E.height,C=E.width,c=void 0,D=void 0,a=void 0,B=void 0;if("left"===A||"right"===A){D=T.top+(T.height-o)/2,c="left"===A?T.left-C:T.left+T.width;var Q=e(D,o,I,g);D+=Q,B=50*(1-2*Q/o)+"%",a=void 0}else{if("top"!==A&&"bottom"!==A)throw new Error('calcOverlayPosition(): No such placement of "'+A+'" found.');c=T.left+(T.width-C)/2,D="top"===A?T.top-o:T.top+T.height;var r=i(c,C,I,g);c+=r,a=50*(1-2*r/C)+"%",B=void 0}return{positionLeft:c,positionTop:D,arrowOffsetLeft:a,arrowOffsetTop:B}}Object.defineProperty(M,"__esModule",{value:!0}),M.default=T;var E=t(143),N=I(E),o=t(287),n=I(o),C=t(144),c=I(C),D=t(52),a=I(D);A.exports=M.default},function(A,M){"use strict";function t(A,M){M&&(A?M.setAttribute("aria-hidden","true"):M.removeAttribute("aria-hidden"))}function I(A,M){T(A,M,function(A){return t(!0,A)})}function g(A,M){T(A,M,function(A){return t(!1,A)})}Object.defineProperty(M,"__esModule",{value:!0}),M.ariaHidden=t,M.hideSiblings=I,M.showSiblings=g;var e=["template","script","style"],i=function(A){var M=A.nodeType,t=A.tagName;return 1===M&&e.indexOf(t.toLowerCase())===-1},T=function(A,M,t){M=[].concat(M),[].forEach.call(A.children,function(A){M.indexOf(A)===-1&&i(A)&&t(A)})}},function(A,M,t){"use strict";var I=function(){};A.exports=I},function(A,M,t){"use strict";function I(A){return A&&A.__esModule?A:{default:A}}function g(A,M){return function(t,I,g){if(null!=t[I]){var e='"'+I+'" property of "'+g+'" has been deprecated.\n'+M;E[e]||(T.default(!1,e),E[e]=!0)}return A(t,I,g)}}function e(){E={}}M.__esModule=!0,M.default=g;var i=t(127),T=I(i),E={};g._resetWarned=e,A.exports=M.default},function(A,M,t){"use strict";function I(A){return A&&A.__esModule?A:{default:A}}function g(A,M){if(!(A instanceof M))throw new TypeError("Cannot call a class as a function")}function e(A,M){if(!A)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!M||"object"!=typeof M&&"function"!=typeof M?A:M}function i(A,M){if("function"!=typeof M&&null!==M)throw new TypeError("Super expression must either be null or a function, not "+typeof M);A.prototype=Object.create(M&&M.prototype,{constructor:{value:A,enumerable:!1,writable:!0,configurable:!0}}),M&&(Object.setPrototypeOf?Object.setPrototypeOf(A,M):A.__proto__=M)}M.__esModule=!0,M.default=void 0;var T=t(1),E=t(184),N=I(E),o=t(185),n=(I(o),function(A){function M(t,I){g(this,M);var i=e(this,A.call(this,t,I));return i.store=t.store,i}return i(M,A),M.prototype.getChildContext=function(){return{store:this.store}},M.prototype.render=function(){return T.Children.only(this.props.children)},M}(T.Component));M.default=n,n.propTypes={store:N.default.isRequired,children:T.PropTypes.element.isRequired},n.childContextTypes={store:N.default.isRequired}},function(A,M){"use strict";function t(A,M){if(A===M)return!0;var t=Object.keys(A),I=Object.keys(M);if(t.length!==I.length)return!1;for(var g=Object.prototype.hasOwnProperty,e=0;e=0||Object.prototype.hasOwnProperty.call(A,I)&&(t[I]=A[I]);return t}function e(A,M){var t=A.history,I=A.routes,e=A.location,E=g(A,["history","routes","location"]);t||e?void 0:(0,N.default)(!1),t=t?t:(0,n.default)(E);var o=(0,c.default)(t,(0,D.createRoutes)(I)),C=void 0;e?e=t.createLocation(e):C=t.listen(function(A){e=A});var B=(0,a.createRouterObject)(t,o);t=(0,a.createRoutingHistory)(t,o),o.match(e,function(A,I,g){M(A,I&&B.createLocation(I,T.REPLACE),g&&i({},g,{history:t,router:B,matchContext:{history:t,transitionManager:o,router:B}})),C&&C()})}M.__esModule=!0;var i=Object.assign||function(A){for(var M=1;M=0||Object.prototype.hasOwnProperty.call(A,I)&&(t[I]=A[I]);return t}function e(A){return function(){var M=arguments.length<=0||void 0===arguments[0]?{}:arguments[0],t=M.routes,I=g(M,["routes"]),e=(0,E.default)(A)(I),T=(0,o.default)(e,t);return i({},e,T)}}M.__esModule=!0;var i=Object.assign||function(A){for(var M=1;M=A&&N&&(T=!0,t()))}}var i=0,T=!1,E=!1,N=!1,o=void 0;e()}M.__esModule=!0;var I=Array.prototype.slice;M.loopAsync=t},function(A,M,t){"use strict";function I(A){return A&&A.__esModule?A:{default:A}}function g(){function A(A){try{A=A||window.history.state||{}}catch(M){A={}}var M=n.getWindowPath(),t=A,I=t.key,g=void 0;I?g=C.readState(I):(g=null,I=s.createKey(),Q&&window.history.replaceState(e({},A,{key:I}),null));var i=N.parsePath(M);return s.createLocation(e({},i,{state:g}),void 0,I)}function M(M){function t(M){void 0!==M.state&&I(A(M.state))}var I=M.transitionTo;return n.addEventListener(window,"popstate",t),function(){n.removeEventListener(window,"popstate",t)}}function t(A){var M=A.basename,t=A.pathname,I=A.search,g=A.hash,e=A.state,i=A.action,T=A.key;if(i!==E.POP){C.saveState(T,e);var N=(M||"")+t+I+g,o={key:T};if(i===E.PUSH){if(r)return window.location.href=N,!1;window.history.pushState(o,null,N)}else{if(r)return window.location.replace(N),!1;window.history.replaceState(o,null,N)}}}function I(A){1===++x&&(j=M(s));var t=s.listenBefore(A);return function(){t(),0===--x&&j()}}function g(A){1===++x&&(j=M(s));var t=s.listen(A);return function(){t(),0===--x&&j()}}function i(A){1===++x&&(j=M(s)),s.registerTransitionHook(A)}function c(A){s.unregisterTransitionHook(A),0===--x&&j()}var a=arguments.length<=0||void 0===arguments[0]?{}:arguments[0];o.canUseDOM?void 0:T.default(!1);var B=a.forceRefresh,Q=n.supportsHistory(),r=!Q||B,s=D.default(e({},a,{getCurrentLocation:A,finishTransition:t,saveState:C.saveState})),x=0,j=void 0;return e({},s,{listenBefore:I,listen:g,registerTransitionHook:i,unregisterTransitionHook:c})}M.__esModule=!0;var e=Object.assign||function(A){for(var M=1;M=0&&M=0&&B8&&u<=11),l=32,Y=String.fromCharCode(l),d=c.topLevelTypes,h={beforeInput:{phasedRegistrationNames:{bubbled:s({onBeforeInput:null}),captured:s({onBeforeInputCapture:null})},dependencies:[d.topCompositionEnd,d.topKeyPress,d.topTextInput,d.topPaste]},compositionEnd:{phasedRegistrationNames:{bubbled:s({onCompositionEnd:null}),captured:s({onCompositionEndCapture:null})},dependencies:[d.topBlur,d.topCompositionEnd,d.topKeyDown,d.topKeyPress,d.topKeyUp,d.topMouseDown]},compositionStart:{phasedRegistrationNames:{bubbled:s({onCompositionStart:null}),captured:s({onCompositionStartCapture:null})},dependencies:[d.topBlur,d.topCompositionStart,d.topKeyDown,d.topKeyPress,d.topKeyUp,d.topMouseDown]},compositionUpdate:{phasedRegistrationNames:{bubbled:s({onCompositionUpdate:null}),captured:s({onCompositionUpdateCapture:null})},dependencies:[d.topBlur,d.topCompositionUpdate,d.topKeyDown,d.topKeyPress,d.topKeyUp,d.topMouseDown]}},S=!1,z=null,p={eventTypes:h,extractEvents:function(A,M,t,I,g){return[N(A,M,t,I,g),C(A,M,t,I,g)]}};A.exports=p},function(A,M,t){"use strict";var I=t(202),g=t(10),e=t(17),i=(t(297),t(476)),T=t(302),E=t(306),N=(t(3),E(function(A){return T(A)})),o=!1,n="cssFloat";if(g.canUseDOM){var C=document.createElement("div").style;try{C.font=""}catch(A){o=!0}void 0===document.documentElement.style.cssFloat&&(n="styleFloat")}var c={createMarkupForStyles:function(A){var M="";for(var t in A)if(A.hasOwnProperty(t)){var I=A[t];null!=I&&(M+=N(t)+":",M+=i(t,I)+";")}return M||null},setValueForStyles:function(A,M){var t=A.style;for(var g in M)if(M.hasOwnProperty(g)){var e=i(g,M[g]);if("float"===g&&(g=n),e)t[g]=e;else{var T=o&&I.shorthandPropertyExpansions[g];if(T)for(var E in T)t[E]="";else t[g]=""}}}};e.measureMethods(c,"CSSPropertyOperations",{setValueForStyles:"setValueForStyles"}),A.exports=c},function(A,M,t){"use strict";function I(A){var M=A.nodeName&&A.nodeName.toLowerCase();return"select"===M||"input"===M&&"file"===A.type}function g(A){var M=u.getPooled(h.change,z,A,w(A));x.accumulateTwoPhaseDispatches(M),y.batchedUpdates(e,M)}function e(A){s.enqueueEvents(A),s.processEventQueue(!1)}function i(A,M){S=A,z=M,S.attachEvent("onchange",g)}function T(){S&&(S.detachEvent("onchange",g),S=null,z=null)}function E(A,M,t){if(A===d.topChange)return t}function N(A,M,t){A===d.topFocus?(T(),i(M,t)):A===d.topBlur&&T()}function o(A,M){S=A,z=M,p=A.value,U=Object.getOwnPropertyDescriptor(A.constructor.prototype,"value"),Object.defineProperty(S,"value",F),S.attachEvent("onpropertychange",C)}function n(){S&&(delete S.value,S.detachEvent("onpropertychange",C),S=null,z=null,p=null,U=null)}function C(A){if("value"===A.propertyName){var M=A.srcElement.value;M!==p&&(p=M,g(A))}}function c(A,M,t){if(A===d.topInput)return t}function D(A,M,t){A===d.topFocus?(n(),o(M,t)):A===d.topBlur&&n()}function a(A,M,t){if((A===d.topSelectionChange||A===d.topKeyUp||A===d.topKeyDown)&&S&&S.value!==p)return p=S.value,z}function B(A){return A.nodeName&&"input"===A.nodeName.toLowerCase()&&("checkbox"===A.type||"radio"===A.type)}function Q(A,M,t){if(A===d.topClick)return t}var r=t(24),s=t(53),x=t(54),j=t(10),y=t(18),u=t(37),w=t(117),L=t(120),l=t(229),Y=t(27),d=r.topLevelTypes,h={change:{phasedRegistrationNames:{bubbled:Y({onChange:null}),captured:Y({onChangeCapture:null})},dependencies:[d.topBlur,d.topChange,d.topClick,d.topFocus,d.topInput,d.topKeyDown,d.topKeyUp,d.topSelectionChange]}},S=null,z=null,p=null,U=null,O=!1;j.canUseDOM&&(O=L("change")&&(!("documentMode"in document)||document.documentMode>8));var m=!1;j.canUseDOM&&(m=L("input")&&(!("documentMode"in document)||document.documentMode>9));var F={get:function(){return U.get.call(this)},set:function(A){p=""+A,U.set.call(this,A)}},f={eventTypes:h,extractEvents:function(A,M,t,g,e){var i,T;if(I(M)?O?i=E:T=N:l(M)?m?i=c:(i=a,T=D):B(M)&&(i=Q),i){var o=i(A,M,t);if(o){var n=u.getPooled(h.change,o,g,e);return n.type="change",x.accumulateTwoPhaseDispatches(n),n}}T&&T(A,M,t)}};A.exports=f},function(A,M){"use strict";var t=0,I={createReactRootIndex:function(){return t++}};A.exports=I},function(A,M,t){"use strict";function I(A){return A.substring(1,A.indexOf(" "))}var g=t(10),e=t(299),i=t(20),T=t(151),E=t(2),N=/^(<[^ \/>]+)/,o="data-danger-index",n={dangerouslyRenderMarkup:function(A){g.canUseDOM?void 0:E(!1);for(var M,t={},n=0;n1?1-M:void 0;return this._fallbackText=g.slice(A,T),this._fallbackText}}),g.addPoolingTo(I),A.exports=I},function(A,M,t){"use strict";var I,g=t(47),e=t(10),i=g.injection.MUST_USE_ATTRIBUTE,T=g.injection.MUST_USE_PROPERTY,E=g.injection.HAS_BOOLEAN_VALUE,N=g.injection.HAS_SIDE_EFFECTS,o=g.injection.HAS_NUMERIC_VALUE,n=g.injection.HAS_POSITIVE_NUMERIC_VALUE,C=g.injection.HAS_OVERLOADED_BOOLEAN_VALUE;if(e.canUseDOM){var c=document.implementation;I=c&&c.hasFeature&&c.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure","1.1")}var D={isCustomAttribute:RegExp.prototype.test.bind(/^(data|aria)-[a-z_][a-z\d_.\-]*$/),Properties:{accept:null,acceptCharset:null,accessKey:null,action:null,allowFullScreen:i|E,allowTransparency:i,alt:null,async:E,autoComplete:null,autoPlay:E,capture:i|E,cellPadding:null,cellSpacing:null,charSet:i,challenge:i,checked:T|E,classID:i,className:I?i:T,cols:i|n,colSpan:null,content:null,contentEditable:null,contextMenu:i,controls:T|E,coords:null,crossOrigin:null,data:null,dateTime:i,default:E,defer:E,dir:null,disabled:i|E,download:C,draggable:null,encType:null,form:i,formAction:i,formEncType:i,formMethod:i,formNoValidate:E,formTarget:i,frameBorder:i,headers:null,height:i,hidden:i|E,high:null,href:null,hrefLang:null,htmlFor:null,httpEquiv:null,icon:null,id:T,inputMode:i,integrity:null,is:i,keyParams:i,keyType:i,kind:null,label:null,lang:null,list:i,loop:T|E,low:null,manifest:i,marginHeight:null,marginWidth:null,max:null,maxLength:i,media:i,mediaGroup:null,method:null,min:null,minLength:i,multiple:T|E,muted:T|E,name:null,nonce:i,noValidate:E,open:E,optimum:null,pattern:null,placeholder:null,poster:null,preload:null,radioGroup:null,readOnly:T|E,rel:null,required:E,reversed:E,role:i,rows:i|n,rowSpan:null,sandbox:null,scope:null,scoped:E,scrolling:null,seamless:i|E,selected:T|E,shape:null,size:i|n,sizes:i,span:n,spellCheck:null,src:null,srcDoc:T,srcLang:null,srcSet:i,start:o,step:null,style:null,summary:null,tabIndex:null,target:null,title:null,type:null,useMap:null,value:T|N,width:i,wmode:i,wrap:null,about:i,datatype:i,inlist:i,prefix:i,property:i,resource:i,typeof:i,vocab:i,autoCapitalize:i,autoCorrect:i,autoSave:null,color:null,itemProp:i,itemScope:i|E,itemType:i,itemID:i,itemRef:i,results:null,security:i,unselectable:i},DOMAttributeNames:{acceptCharset:"accept-charset",className:"class",htmlFor:"for",httpEquiv:"http-equiv"},DOMPropertyNames:{autoComplete:"autocomplete",autoFocus:"autofocus",autoPlay:"autoplay",autoSave:"autosave",encType:"encoding",hrefLang:"hreflang",radioGroup:"radiogroup",spellCheck:"spellcheck",srcDoc:"srcdoc",srcSet:"srcset"}};A.exports=D},function(A,M,t){"use strict";var I=t(208),g=t(450),e=t(455),i=t(4),T=t(477),E={};i(E,e),i(E,{findDOMNode:T("findDOMNode","ReactDOM","react-dom",I,I.findDOMNode),render:T("render","ReactDOM","react-dom",I,I.render),unmountComponentAtNode:T("unmountComponentAtNode","ReactDOM","react-dom",I,I.unmountComponentAtNode),renderToString:T("renderToString","ReactDOMServer","react-dom/server",g,g.renderToString),renderToStaticMarkup:T("renderToStaticMarkup","ReactDOMServer","react-dom/server",g,g.renderToStaticMarkup)}),E.__SECRET_DOM_DO_NOT_USE_OR_YOU_WILL_BE_FIRED=I,E.__SECRET_DOM_SERVER_DO_NOT_USE_OR_YOU_WILL_BE_FIRED=g,A.exports=E},function(A,M,t){"use strict";var I=(t(55),t(114)),g=(t(3),"_getDOMNodeDidWarn"),e={getDOMNode:function(){return this.constructor[g]=!0,I(this)}};A.exports=e},function(A,M,t){"use strict";function I(A,M,t){var I=void 0===A[t];null!=M&&I&&(A[t]=e(M,null))}var g=t(36),e=t(119),i=t(122),T=t(123),E=(t(3),{instantiateChildren:function(A,M,t){if(null==A)return null;var g={};return T(A,I,g),g},updateChildren:function(A,M,t,I){if(!M&&!A)return null;var T;for(T in M)if(M.hasOwnProperty(T)){var E=A&&A[T],N=E&&E._currentElement,o=M[T];if(null!=E&&i(N,o))g.receiveComponent(E,o,t,I),M[T]=E;else{E&&g.unmountComponent(E,T);var n=e(o,null);M[T]=n}}for(T in A)!A.hasOwnProperty(T)||M&&M.hasOwnProperty(T)||g.unmountComponent(A[T]);return M},unmountChildren:function(A){for(var M in A)if(A.hasOwnProperty(M)){var t=A[M];g.unmountComponent(t)}}});A.exports=E},function(A,M,t){"use strict";function I(A){var M=A._currentElement._owner||null;if(M){var t=M.getName();if(t)return" Check the render method of ` + "`" + `"+t+"` + "`" + `."}return""}function g(A){}var e=t(110),i=t(25),T=t(13),E=t(55),N=t(17),o=t(71),n=(t(70),t(36)),C=t(112),c=t(4),D=t(50),a=t(2),B=t(122);t(3);g.prototype.render=function(){var A=E.get(this)._currentElement.type;return A(this.props,this.context,this.updater)};var Q=1,r={construct:function(A){this._currentElement=A,this._rootNodeID=null,this._instance=null,this._pendingElement=null,this._pendingStateQueue=null,this._pendingReplaceState=!1,this._pendingForceUpdate=!1,this._renderedComponent=null,this._context=null,this._mountOrder=0,this._topLevelWrapper=null,this._pendingCallbacks=null},mountComponent:function(A,M,t){this._context=t,this._mountOrder=Q++,this._rootNodeID=A;var I,e,i=this._processProps(this._currentElement.props),N=this._processContext(t),o=this._currentElement.type,c="prototype"in o;c&&(I=new o(i,N,C)),c&&null!==I&&I!==!1&&!T.isValidElement(I)||(e=I,I=new g(o)),I.props=i,I.context=N,I.refs=D,I.updater=C,this._instance=I,E.set(I,this);var B=I.state;void 0===B&&(I.state=B=null),"object"!=typeof B||Array.isArray(B)?a(!1):void 0,this._pendingStateQueue=null,this._pendingReplaceState=!1,this._pendingForceUpdate=!1,I.componentWillMount&&(I.componentWillMount(),this._pendingStateQueue&&(I.state=this._processPendingState(I.props,I.context))),void 0===e&&(e=this._renderValidatedComponent()),this._renderedComponent=this._instantiateReactComponent(e);var r=n.mountComponent(this._renderedComponent,A,M,this._processChildContext(t));return I.componentDidMount&&M.getReactMountReady().enqueue(I.componentDidMount,I),r},unmountComponent:function(){var A=this._instance;A.componentWillUnmount&&A.componentWillUnmount(),n.unmountComponent(this._renderedComponent),this._renderedComponent=null,this._instance=null,this._pendingStateQueue=null,this._pendingReplaceState=!1,this._pendingForceUpdate=!1,this._pendingCallbacks=null,this._pendingElement=null,this._context=null,this._rootNodeID=null,this._topLevelWrapper=null,E.remove(A)},_maskContext:function(A){var M=null,t=this._currentElement.type,I=t.contextTypes;if(!I)return D;M={};for(var g in I)M[g]=A[g];return M},_processContext:function(A){var M=this._maskContext(A);return M},_processChildContext:function(A){var M=this._currentElement.type,t=this._instance,I=t.getChildContext&&t.getChildContext();if(I){"object"!=typeof M.childContextTypes?a(!1):void 0;for(var g in I)g in M.childContextTypes?void 0:a(!1);return c({},A,I)}return A},_processProps:function(A){return A},_checkPropTypes:function(A,M,t){var g=this.getName();for(var e in A)if(A.hasOwnProperty(e)){var i;try{"function"!=typeof A[e]?a(!1):void 0,i=A[e](M,e,g,t)}catch(A){i=A}if(i instanceof Error){I(this);t===o.prop}}},receiveComponent:function(A,M,t){var I=this._currentElement,g=this._context;this._pendingElement=null,this.updateComponent(M,I,A,g,t)},performUpdateIfNecessary:function(A){null!=this._pendingElement&&n.receiveComponent(this,this._pendingElement||this._currentElement,A,this._context),(null!==this._pendingStateQueue||this._pendingForceUpdate)&&this.updateComponent(A,this._currentElement,this._currentElement,this._context,this._context)},updateComponent:function(A,M,t,I,g){var e,i=this._instance,T=this._context===g?i.context:this._processContext(g);M===t?e=t.props:(e=this._processProps(t.props),i.componentWillReceiveProps&&i.componentWillReceiveProps(e,T));var E=this._processPendingState(e,T),N=this._pendingForceUpdate||!i.shouldComponentUpdate||i.shouldComponentUpdate(e,E,T);N?(this._pendingForceUpdate=!1,this._performComponentUpdate(t,e,E,T,A,g)):(this._currentElement=t,this._context=g,i.props=e,i.state=E,i.context=T)},_processPendingState:function(A,M){var t=this._instance,I=this._pendingStateQueue,g=this._pendingReplaceState;if(this._pendingReplaceState=!1,this._pendingStateQueue=null,!I)return t.state;if(g&&1===I.length)return I[0];for(var e=c({},g?I[0]:t.state),i=g?1:0;i=0||null!=M.is}function B(A){D(A),this._tag=A.toLowerCase(),this._renderedChildren=null,this._previousStyle=null,this._previousStyleCopy=null,this._rootNodeID=null,this._wrapperState=null,this._topLevelWrapper=null,this._nodeWithLegacyProperties=null}var Q=t(429),r=t(431),s=t(47),x=t(107),j=t(24),y=t(69),u=t(109),w=t(444),L=t(447),l=t(448),Y=t(210),d=t(451),h=t(12),S=t(456),z=t(17),p=t(112),U=t(4),O=t(74),m=t(75),F=t(2),f=(t(120),t(27)),k=t(76),R=t(121),J=(t(152),t(124),t(3),y.deleteListener),G=y.listenTo,H=y.registrationNameModules,b={string:!0,number:!0},X=f({children:null}),v=f({style:null}),W=f({__html:null}),V=1,P={topAbort:"abort",topCanPlay:"canplay",topCanPlayThrough:"canplaythrough",topDurationChange:"durationchange",topEmptied:"emptied",topEncrypted:"encrypted",topEnded:"ended",topError:"error",topLoadedData:"loadeddata",topLoadedMetadata:"loadedmetadata",topLoadStart:"loadstart",topPause:"pause",topPlay:"play",topPlaying:"playing",topProgress:"progress",topRateChange:"ratechange",topSeeked:"seeked",topSeeking:"seeking",topStalled:"stalled",topSuspend:"suspend",topTimeUpdate:"timeupdate",topVolumeChange:"volumechange",topWaiting:"waiting"},Z={area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0},K={listing:!0,pre:!0,textarea:!0},q=(U({menuitem:!0},Z),/^[a-zA-Z][a-zA-Z:_\.\-\d]*$/),_={},$={}.hasOwnProperty;B.displayName="ReactDOMComponent",B.Mixin={construct:function(A){this._currentElement=A},mountComponent:function(A,M,t){this._rootNodeID=A;var I=this._currentElement.props;switch(this._tag){case"iframe":case"img":case"form":case"video":case"audio":this._wrapperState={listeners:null},M.getReactMountReady().enqueue(n,this);break;case"button":I=w.getNativeProps(this,I,t);break;case"input":L.mountWrapper(this,I,t),I=L.getNativeProps(this,I,t);break;case"option":l.mountWrapper(this,I,t),I=l.getNativeProps(this,I,t);break;case"select":Y.mountWrapper(this,I,t),I=Y.getNativeProps(this,I,t),t=Y.processChildContext(this,I,t);break;case"textarea":d.mountWrapper(this,I,t),I=d.getNativeProps(this,I,t)}E(this,I);var g;if(M.useCreateElement){var e=t[h.ownerDocumentContextKey],i=e.createElement(this._currentElement.type);x.setAttributeForID(i,this._rootNodeID),h.getID(i),this._updateDOMProperties({},I,M,i),this._createInitialChildren(M,I,t,i),g=i}else{var T=this._createOpenTagMarkupAndPutListeners(M,I),N=this._createContentMarkup(M,I,t);g=!N&&Z[this._tag]?T+"/>":T+">"+N+""}switch(this._tag){case"input":M.getReactMountReady().enqueue(C,this);case"button":case"select":case"textarea":I.autoFocus&&M.getReactMountReady().enqueue(Q.focusDOMComponent,this)}return g},_createOpenTagMarkupAndPutListeners:function(A,M){var t="<"+this._currentElement.type;for(var I in M)if(M.hasOwnProperty(I)){var g=M[I];if(null!=g)if(H.hasOwnProperty(I))g&&N(this._rootNodeID,I,g,A);else{I===v&&(g&&(g=this._previousStyleCopy=U({},M.style)),g=r.createMarkupForStyles(g));var e=null;null!=this._tag&&a(this._tag,M)?I!==X&&(e=x.createMarkupForCustomAttribute(I,g)):e=x.createMarkupForProperty(I,g),e&&(t+=" "+e)}}if(A.renderToStaticMarkup)return t;var i=x.createMarkupForID(this._rootNodeID);return t+" "+i},_createContentMarkup:function(A,M,t){var I="",g=M.dangerouslySetInnerHTML;if(null!=g)null!=g.__html&&(I=g.__html);else{var e=b[typeof M.children]?M.children:null,i=null!=e?null:M.children;if(null!=e)I=m(e);else if(null!=i){var T=this.mountChildren(i,A,t);I=T.join("")}}return K[this._tag]&&"\n"===I.charAt(0)?"\n"+I:I},_createInitialChildren:function(A,M,t,I){var g=M.dangerouslySetInnerHTML;if(null!=g)null!=g.__html&&k(I,g.__html);else{var e=b[typeof M.children]?M.children:null,i=null!=e?null:M.children;if(null!=e)R(I,e);else if(null!=i)for(var T=this.mountChildren(i,A,t),E=0;EM.end?(t=M.end,I=M.start):(t=M.start,I=M.end),g.moveToElementText(A),g.moveStart("character",t),g.setEndPoint("EndToStart",g),g.moveEnd("character",I-t),g.select()}function T(A,M){if(window.getSelection){var t=window.getSelection(),I=A[o()].length,g=Math.min(M.start,I),e="undefined"==typeof M.end?g:Math.min(M.end,I);if(!t.extend&&g>e){var i=e;e=g,g=i}var T=N(A,g),E=N(A,e);if(T&&E){var n=document.createRange();n.setStart(T.node,T.offset),t.removeAllRanges(),g>e?(t.addRange(n),t.extend(E.node,E.offset)):(n.setEnd(E.node,E.offset),t.addRange(n))}}}var E=t(10),N=t(480),o=t(228),n=E.canUseDOM&&"selection"in document&&!("getSelection"in window),C={getOffsets:n?g:e,setOffsets:n?i:T};A.exports=C},function(A,M,t){"use strict";var I=t(213),g=t(461),e=t(113);I.inject();var i={renderToString:g.renderToString,renderToStaticMarkup:g.renderToStaticMarkup,version:e};A.exports=i},function(A,M,t){"use strict";function I(){this._rootNodeID&&o.updateWrapper(this)}function g(A){var M=this._currentElement.props,t=e.executeOnChange(M,A);return T.asap(I,this),t}var e=t(108),i=t(111),T=t(18),E=t(4),N=t(2),o=(t(3),{getNativeProps:function(A,M,t){null!=M.dangerouslySetInnerHTML?N(!1):void 0;var I=E({},M,{defaultValue:void 0,value:void 0,children:A._wrapperState.initialValue,onChange:A._wrapperState.onChange});return I},mountWrapper:function(A,M){var t=M.defaultValue,I=M.children;null!=I&&(null!=t?N(!1):void 0,Array.isArray(I)&&(I.length<=1?void 0:N(!1),I=I[0]),t=""+I),null==t&&(t="");var i=e.getValue(M);A._wrapperState={initialValue:""+(null!=i?i:t),onChange:g.bind(A)}},updateWrapper:function(A){var M=A._currentElement.props,t=e.getValue(M);null!=t&&i.updatePropertyByID(A._rootNodeID,"value",""+t)}});A.exports=o},function(A,M,t){"use strict";function I(A){g.enqueueEvents(A),g.processEventQueue(!1)}var g=t(53),e={handleTopLevel:function(A,M,t,e,i){var T=g.extractEvents(A,M,t,e,i);I(T)}};A.exports=e},function(A,M,t){"use strict";function I(A){var M=C.getID(A),t=n.getReactRootIDFromNodeID(M),I=C.findReactContainerForID(t),g=C.getFirstReactDOM(I);return g}function g(A,M){this.topLevelType=A,this.nativeEvent=M,this.ancestors=[]}function e(A){i(A)}function i(A){for(var M=C.getFirstReactDOM(a(A.nativeEvent))||window,t=M;t;)A.ancestors.push(t),t=I(t);for(var g=0;g=M)return{node:g,offset:M-e};e=i}g=t(I(g))}}A.exports=g},function(A,M,t){"use strict";function I(A){return g.isValidElement(A)?void 0:e(!1),A}var g=t(13),e=t(2);A.exports=I},function(A,M,t){"use strict";function I(A){return'"'+g(A)+'"'}var g=t(75);A.exports=I},function(A,M,t){"use strict";var I=t(12);A.exports=I.renderSubtreeIntoContainer},function(A,M){A.exports=function(A,M,t){for(var I=0,g=A.length,e=3==arguments.length?t:A[I++];I=400){ -var T="cannot "+M.method+" "+M.url+" ("+i.status+")";A=new e(T),A.status=i.status,A.body=i.body,A.res=i,I(A)}else g?I(new e(g)):t(i)})})},g.prototype.then=function(){var A=this.promise();return A.then.apply(A,arguments)}},function(A,M,t){function I(){}function g(A){var M={}.toString.call(A);switch(M){case"[object File]":case"[object Blob]":case"[object FormData]":return!0;default:return!1}}function e(A){if(!s(A))return A;var M=[];for(var t in A)null!=A[t]&&i(M,t,A[t]);return M.join("&")}function i(A,M,t){return Array.isArray(t)?t.forEach(function(t){i(A,M,t)}):void A.push(encodeURIComponent(M)+"="+encodeURIComponent(t))}function T(A){for(var M,t,I={},g=A.split("&"),e=0,i=g.length;e=200&&M.status<300)return t.callback(A,M);var I=new Error(M.statusText||"Unsuccessful HTTP response");I.original=A,I.response=M,I.status=M.status,t.callback(I,M)})}function D(A,M){var t=x("DELETE",A);return M&&t.end(M),t}var a,B=t(273),Q=t(484),r=t(502),s=t(236);a="undefined"!=typeof window?window:"undefined"!=typeof self?self:this;var x=A.exports=t(503).bind(null,c);x.getXHR=function(){if(!(!a.XMLHttpRequest||a.location&&"file:"==a.location.protocol&&a.ActiveXObject))return new XMLHttpRequest;try{return new ActiveXObject("Microsoft.XMLHTTP")}catch(A){}try{return new ActiveXObject("Msxml2.XMLHTTP.6.0")}catch(A){}try{return new ActiveXObject("Msxml2.XMLHTTP.3.0")}catch(A){}try{return new ActiveXObject("Msxml2.XMLHTTP")}catch(A){}return!1};var j="".trim?function(A){return A.trim()}:function(A){return A.replace(/(^\s*|\s*$)/g,"")};x.serializeObject=e,x.parseString=T,x.types={html:"text/html",json:"application/json",xml:"application/xml",urlencoded:"application/x-www-form-urlencoded",form:"application/x-www-form-urlencoded","form-data":"application/x-www-form-urlencoded"},x.serialize={"application/x-www-form-urlencoded":e,"application/json":JSON.stringify},x.parse={"application/x-www-form-urlencoded":T,"application/json":JSON.parse},C.prototype.get=function(A){return this.header[A.toLowerCase()]},C.prototype.setHeaderProperties=function(A){var M=this.header["content-type"]||"";this.type=o(M);var t=n(M);for(var I in t)this[I]=t[I]},C.prototype.parseBody=function(A){var M=x.parse[this.type];return!M&&N(this.type)&&(M=x.parse["application/json"]),M&&A&&(A.length||A instanceof Object)?M(A):null},C.prototype.setStatusProperties=function(A){1223===A&&(A=204);var M=A/100|0;this.status=this.statusCode=A,this.statusType=M,this.info=1==M,this.ok=2==M,this.clientError=4==M,this.serverError=5==M,this.error=(4==M||5==M)&&this.toError(),this.accepted=202==A,this.noContent=204==A,this.badRequest=400==A,this.unauthorized=401==A,this.notAcceptable=406==A,this.notFound=404==A,this.forbidden=403==A},C.prototype.toError=function(){var A=this.req,M=A.method,t=A.url,I="cannot "+M+" "+t+" ("+this.status+")",g=new Error(I);return g.status=this.status,g.method=M,g.url=t,g},x.Response=C,B(c.prototype);for(var y in r)c.prototype[y]=r[y];c.prototype.abort=function(){if(!this.aborted)return this.aborted=!0,this.xhr&&this.xhr.abort(),this.clearTimeout(),this.emit("abort"),this},c.prototype.type=function(A){return this.set("Content-Type",x.types[A]||A),this},c.prototype.responseType=function(A){return this._responseType=A,this},c.prototype.accept=function(A){return this.set("Accept",x.types[A]||A),this},c.prototype.auth=function(A,M,t){switch(t||(t={type:"basic"}),t.type){case"basic":var I=btoa(A+":"+M);this.set("Authorization","Basic "+I);break;case"auto":this.username=A,this.password=M}return this},c.prototype.query=function(A){return"string"!=typeof A&&(A=e(A)),A&&this._query.push(A),this},c.prototype.attach=function(A,M,t){return this._getFormData().append(A,M,t||M.name),this},c.prototype._getFormData=function(){return this._formData||(this._formData=new a.FormData),this._formData},c.prototype.send=function(A){var M=s(A),t=this._header["content-type"];if(M&&s(this._data))for(var I in A)this._data[I]=A[I];else"string"==typeof A?(t||this.type("form"),t=this._header["content-type"],"application/x-www-form-urlencoded"==t?this._data=this._data?this._data+"&"+A:A:this._data=(this._data||"")+A):this._data=A;return!M||g(A)?this:(t||this.type("json"),this)},C.prototype.parse=function(A){return a.console&&console.warn("Client-side parse() method has been renamed to serialize(). This method is not compatible with superagent v2.0"),this.serialize(A),this},C.prototype.serialize=function(A){return this._parser=A,this},c.prototype.callback=function(A,M){var t=this._callback;this.clearTimeout(),t(A,M)},c.prototype.crossDomainError=function(){var A=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.");A.crossDomain=!0,A.status=this.status,A.method=this.method,A.url=this.url,this.callback(A)},c.prototype.timeoutError=function(){var A=this._timeout,M=new Error("timeout of "+A+"ms exceeded");M.timeout=A,this.callback(M)},c.prototype.withCredentials=function(){return this._withCredentials=!0,this},c.prototype.end=function(A){var M=this,t=this.xhr=x.getXHR(),e=this._query.join("&"),i=this._timeout,T=this._formData||this._data;this._callback=A||I,t.onreadystatechange=function(){if(4==t.readyState){var A;try{A=t.status}catch(M){A=0}if(0==A){if(M.timedout)return M.timeoutError();if(M.aborted)return;return M.crossDomainError()}M.emit("end")}};var E=function(A){A.total>0&&(A.percent=A.loaded/A.total*100),A.direction="download",M.emit("progress",A)};this.hasListeners("progress")&&(t.onprogress=E);try{t.upload&&this.hasListeners("progress")&&(t.upload.onprogress=E)}catch(A){}if(i&&!this._timer&&(this._timer=setTimeout(function(){M.timedout=!0,M.abort()},i)),e&&(e=x.serializeObject(e),this.url+=~this.url.indexOf("?")?"&"+e:"?"+e),this.username&&this.password?t.open(this.method,this.url,!0,this.username,this.password):t.open(this.method,this.url,!0),this._withCredentials&&(t.withCredentials=!0),"GET"!=this.method&&"HEAD"!=this.method&&"string"!=typeof T&&!g(T)){var o=this._header["content-type"],n=this._parser||x.serialize[o?o.split(";")[0]:""];!n&&N(o)&&(n=x.serialize["application/json"]),n&&(T=n(T))}for(var C in this.header)null!=this.header[C]&&t.setRequestHeader(C,this.header[C]);return this._responseType&&(t.responseType=this._responseType),this.emit("request",this),t.send("undefined"!=typeof T?T:null),this},x.Request=c,x.get=function(A,M,t){var I=x("GET",A);return"function"==typeof M&&(t=M,M=null),M&&I.query(M),t&&I.end(t),I},x.head=function(A,M,t){var I=x("HEAD",A);return"function"==typeof M&&(t=M,M=null),M&&I.send(M),t&&I.end(t),I},x.del=D,x.delete=D,x.patch=function(A,M,t){var I=x("PATCH",A);return"function"==typeof M&&(t=M,M=null),M&&I.send(M),t&&I.end(t),I},x.post=function(A,M,t){var I=x("POST",A);return"function"==typeof M&&(t=M,M=null),M&&I.send(M),t&&I.end(t),I},x.put=function(A,M,t){var I=x("PUT",A);return"function"==typeof M&&(t=M,M=null),M&&I.send(M),t&&I.end(t),I}},function(A,M,t){var I=t(236);M.clearTimeout=function(){return this._timeout=0,clearTimeout(this._timer),this},M.parse=function(A){return this._parser=A,this},M.timeout=function(A){return this._timeout=A,this},M.then=function(A,M){return this.end(function(t,I){t?M(t):A(I)})},M.use=function(A){return A(this),this},M.get=function(A){return this._header[A.toLowerCase()]},M.getHeader=M.get,M.set=function(A,M){if(I(A)){for(var t in A)this.set(t,A[t]);return this}return this._header[A.toLowerCase()]=M,this.header[A]=M,this},M.unset=function(A){return delete this._header[A.toLowerCase()],delete this.header[A],this},M.field=function(A,M){return this._getFormData().append(A,M),this}},function(A,M){function t(A,M,t){return"function"==typeof t?new A("GET",M).end(t):2==arguments.length?new A("GET",M):new A(M,t)}A.exports=t},function(A,M,t){A.exports=t(505)},function(A,M,t){(function(A,I){"use strict";function g(A){return A&&A.__esModule?A:{default:A}}Object.defineProperty(M,"__esModule",{value:!0});var e,i=t(506),T=g(i);e="undefined"!=typeof self?self:"undefined"!=typeof window?window:"undefined"!=typeof A?A:I;var E=(0,T.default)(e);M.default=E}).call(M,function(){return this}(),t(128)(A))},function(A,M){"use strict";function t(A){var M,t=A.Symbol;return"function"==typeof t?t.observable?M=t.observable:(M=t("observable"),t.observable=M):M="@@observable",M}Object.defineProperty(M,"__esModule",{value:!0}),M.default=t},function(A,M,t){function I(A){return g(A).replace(/\s(\w)/g,function(A,M){return M.toUpperCase()})}var g=t(509);A.exports=I},function(A,M){function t(A){return e.test(A)?A.toLowerCase():i.test(A)?(I(A)||A).toLowerCase():T.test(A)?g(A).toLowerCase():A.toLowerCase()}function I(A){return A.replace(E,function(A,M){return M?" "+M:""})}function g(A){return A.replace(N,function(A,M,t){return M+" "+t.toLowerCase().split("").join(" ")})}A.exports=t;var e=/\s/,i=/(_|-|\.|:)/,T=/([a-z][A-Z]|[A-Z][a-z])/,E=/[\W_]+(.|$)/g,N=/(.)([A-Z]+)/g},function(A,M,t){function I(A){return g(A).replace(/[\W_]+(.|$)/g,function(A,M){return M?" "+M:""}).trim()}var g=t(508);A.exports=I},function(A,M){A.exports=function(){var A=document.getSelection();if(!A.rangeCount)return function(){};for(var M=document.activeElement,t=[],I=0;I=0||Object.prototype.hasOwnProperty.call(A,I)&&(t[I]=A[I]);return t}function i(A,M){function t(I,g){function i(A,t){var I=c.getLinkName(A),e=this.props[g[A]];I&&E(this.props,I)&&!e&&(e=this.props[I].requestChange);for(var i=arguments.length,T=Array(i>2?i-2:0),N=2;N=15||0===s[0]&&s[1]>=13?A:A.type}function T(A,M){var t=N(M);return t&&!E(A,M)&&E(A,t)?A[t].value:A[M]}function E(A,M){return void 0!==A[M]}function N(A){return"value"===A?"valueLink":"checked"===A?"checkedLink":null}function o(A){return"default"+A.charAt(0).toUpperCase()+A.substr(1)}function n(A,M,t){return function(){for(var I=arguments.length,g=Array(I),e=0;eo||N===o&&"application/"===M[T].substr(0,12))continue}M[T]=I}}})}var E=t(350),N=t(353).extname,o=/^\s*([^;\s]*)(?:;|\s|$)/,n=/^text\//i;M.charset=I,M.charsets={lookup:I},M.contentType=g,M.extension=e,M.extensions=Object.create(null),M.lookup=i,M.types=Object.create(null),T(M.extensions,M.types)},function(A,M){function t(A){throw new Error("Cannot find module '"+A+"'.")}t.keys=function(){return[]},t.resolve=t,A.exports=t,t.id=352},function(A,M,t){(function(A){function t(A,M){for(var t=0,I=A.length-1;I>=0;I--){var g=A[I];"."===g?A.splice(I,1):".."===g?(A.splice(I,1),t++):t&&(A.splice(I,1),t--)}if(M)for(;t--;t)A.unshift("..");return A}function I(A,M){if(A.filter)return A.filter(M);for(var t=[],I=0;I=-1&&!g;e--){var i=e>=0?arguments[e]:A.cwd();if("string"!=typeof i)throw new TypeError("Arguments to path.resolve must be strings");i&&(M=i+"/"+M,g="/"===i.charAt(0))}return M=t(I(M.split("/"),function(A){return!!A}),!g).join("/"),(g?"/":"")+M||"."},M.normalize=function(A){var g=M.isAbsolute(A),e="/"===i(A,-1);return A=t(I(A.split("/"),function(A){return!!A}),!g).join("/"),A||g||(A="."),A&&e&&(A+="/"),(g?"/":"")+A},M.isAbsolute=function(A){return"/"===A.charAt(0)},M.join=function(){var A=Array.prototype.slice.call(arguments,0);return M.normalize(I(A,function(A,M){if("string"!=typeof A)throw new TypeError("Arguments to path.join must be strings");return A}).join("/"))},M.relative=function(A,t){function I(A){for(var M=0;M=0&&""===A[t];t--);return M>t?[]:A.slice(M,t-M+1)}A=M.resolve(A).substr(1),t=M.resolve(t).substr(1);for(var g=I(A.split("/")),e=I(t.split("/")),i=Math.min(g.length,e.length),T=i,E=0;E=0;e--){var i=I[e]+g;if(i in M)return i}return!1}},function(A,M,t){"use strict";var I=t(496);M.extract=function(A){return A.split("?")[1]||""},M.parse=function(A){return"string"!=typeof A?{}:(A=A.trim().replace(/^(\?|#|&)/,""),A?A.split("&").reduce(function(A,M){var t=M.replace(/\+/g," ").split("="),I=t.shift(),g=t.length>0?t.join("="):void 0;return I=decodeURIComponent(I),g=void 0===g?null:decodeURIComponent(g),A.hasOwnProperty(I)?Array.isArray(A[I])?A[I].push(g):A[I]=[A[I],g]:A[I]=g,A},{}):{})},M.stringify=function(A){return A?Object.keys(A).sort().map(function(M){var t=A[M];return void 0===t?"":null===t?M:Array.isArray(t)?t.slice().sort().map(function(A){return I(M)+"="+I(A)}).join("&"):I(M)+"="+I(t)}).filter(function(A){return A.length>0}).join("&"):""}},function(A,M){"use strict";function t(A,M){return Object.prototype.hasOwnProperty.call(A,M)}A.exports=function(A,M,I,g){M=M||"&",I=I||"=";var e={};if("string"!=typeof A||0===A.length)return e;var i=/\+/g;A=A.split(M);var T=1e3;g&&"number"==typeof g.maxKeys&&(T=g.maxKeys);var E=A.length;T>0&&E>T&&(E=T);for(var N=0;N=0?(o=D.substr(0,a),n=D.substr(a+1)):(o=D,n=""),C=decodeURIComponent(o),c=decodeURIComponent(n),t(e,C)?Array.isArray(e[C])?e[C].push(c):e[C]=[e[C],c]:e[C]=c}return e}},function(A,M){"use strict";var t=function(A){switch(typeof A){case"string":return A;case"boolean":return A?"true":"false";case"number":return isFinite(A)?A:"";default:return""}};A.exports=function(A,M,I,g){return M=M||"&",I=I||"=",null===A&&(A=void 0),"object"==typeof A?Object.keys(A).map(function(g){var e=encodeURIComponent(t(g))+I;return Array.isArray(A[g])?A[g].map(function(A){return e+encodeURIComponent(t(A))}).join(M):e+encodeURIComponent(t(A[g]))}).join(M):g?encodeURIComponent(t(g))+I+encodeURIComponent(t(A)):""}},function(A,M,t){"use strict";M.decode=M.parse=t(357),M.encode=M.stringify=t(358)},function(A,M,t){(function(M){for(var I=t(354),g="undefined"==typeof window?M:window,e=["moz","webkit"],i="AnimationFrame",T=g["request"+i],E=g["cancel"+i]||g["cancelRequest"+i],N=0;!T&&N1)||(e=A,!1)}),e)return new Error("(children) "+I+" - Duplicate children detected of bsRole: "+e+". Only one child each allowed with the following bsRoles: "+M.join(", "))})}},A.exports=M.default},function(A,M,t){"use strict";function I(A){var M=[];return void 0===A?M:(i.default.forEach(A,function(A){M.push(A)}),M)}var g=t(6).default;M.__esModule=!0,M.default=I;var e=t(51),i=g(e);A.exports=M.default},function(A,M,t){"use strict";function I(A){return A&&A.__esModule?A:{default:A}}function g(A,M){var t={};for(var I in A)M.indexOf(I)>=0||Object.prototype.hasOwnProperty.call(A,I)&&(t[I]=A[I]);return t}Object.defineProperty(M,"__esModule",{value:!0}),M.CopyToClipboard=void 0;var e=Object.assign||function(A){for(var M=1;M=0||Object.prototype.hasOwnProperty.call(A,I)&&(t[I]=A[I]);return t}function e(A){var M=A.style,t=g(A,["style"]),I=o({},M,{height:6,right:2,bottom:2,left:2,borderRadius:3});return C.default.createElement("div",o({style:I},t))}function i(A){var M=A.style,t=g(A,["style"]),I=o({},M,{width:6,right:2,bottom:2,top:2,borderRadius:3});return C.default.createElement("div",o({style:I},t))}function T(A){var M=A.style,t=g(A,["style"]),I=o({},M,{cursor:"pointer",borderRadius:"inherit",backgroundColor:"rgba(0,0,0,.2)"});return C.default.createElement("div",o({style:I},t))}function E(A){var M=A.style,t=g(A,["style"]),I=o({},M,{cursor:"pointer",borderRadius:"inherit",backgroundColor:"rgba(0,0,0,.2)"});return C.default.createElement("div",o({style:I},t))}function N(A){return C.default.createElement("div",A)}var o=Object.assign||function(A){for(var M=1;M=0||Object.prototype.hasOwnProperty.call(A,I)&&(t[I]=A[I]);return t}var e=Object.assign||function(A){for(var M=1;M=0||Object.prototype.hasOwnProperty.call(A,I)&&(t[I]=A[I]);return t}function e(A,M){if(!(A instanceof M))throw new TypeError("Cannot call a class as a function")}function i(A,M){if(!A)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!M||"object"!=typeof M&&"function"!=typeof M?A:M}function T(A,M){if("function"!=typeof M&&null!==M)throw new TypeError("Super expression must either be null or a function, not "+typeof M);A.prototype=Object.create(M&&M.prototype,{constructor:{value:A,enumerable:!1,writable:!0,configurable:!0}}),M&&(Object.setPrototypeOf?Object.setPrototypeOf(A,M):A.__proto__=M)}Object.defineProperty(M,"__esModule",{value:!0});var E=Object.assign||function(A){for(var M=1;M0||(this.setState({isDragActive:!1,isDragReject:!1}),this.props.onDragLeave&&this.props.onDragLeave.call(this,A))}},{key:"onDrop",value:function A(M){var t=this,I=this.props,A=I.onDrop,g=I.onDropAccepted,e=I.onDropRejected,i=I.multiple,T=I.disablePreview,E=(0,a.default)(M,i),N=[],o=[];M.preventDefault(),this.enterCounter=0,this.isFileDialogActive=!1,E.forEach(function(A){T||(A.preview=window.URL.createObjectURL(A)),t.fileAccepted(A)&&t.fileMatchSize(A)?N.push(A):o.push(A)}),A&&A.call(this,N,o,M),o.length>0&&e&&e.call(this,o,M),N.length>0&&g&&g.call(this,N,M),this.setState({isDragActive:!1,isDragReject:!1})}},{key:"onClick",value:function A(M){M.stopPropagation();var t=this.props,A=t.onClick,I=t.disableClick;I||(this.open(),A&&A.call(this,M))}},{key:"onFileDialogCancel",value:function A(){var A=this.props.onFileDialogCancel,M=this.fileInputEl,t=this.isFileDialogActive;A&&t&&setTimeout(function(){var I=M.files;I.length||(t=!1,A())},300)}},{key:"fileAccepted",value:function(A){return(0,c.default)(A,this.props.accept)}},{key:"fileMatchSize",value:function(A){return A.size<=this.props.maxSize&&A.size>=this.props.minSize}},{key:"allFilesAccepted",value:function(A){return A.every(this.fileAccepted)}},{key:"open",value:function(){this.isFileDialogActive=!0,this.fileInputEl.value=null,this.fileInputEl.click()}},{key:"render",value:function(){var A=this,M=this.props,t=M.accept,I=M.activeClassName,e=M.inputProps,i=M.multiple,T=M.name,N=M.rejectClassName,o=g(M,["accept","activeClassName","inputProps","multiple","name","rejectClassName"]),C=o.activeStyle,c=o.className,D=o.rejectStyle,a=o.style,Q=g(o,["activeStyle","className","rejectStyle","style"]),r=this.state,s=r.isDragActive,x=r.isDragReject;c=c||"",s&&I&&(c+=" "+I),x&&N&&(c+=" "+N),c||a||C||D||(a={width:200,height:200,borderWidth:2,borderColor:"#666",borderStyle:"dashed",borderRadius:5},C={borderStyle:"solid",backgroundColor:"#eee"},D={borderStyle:"solid",backgroundColor:"#ffdddd"});var j=void 0;j=C&&s?E({},a,C):D&&x?E({},a,D):E({},a);var y={accept:t,type:"file",style:{display:"none"},multiple:B&&i,ref:function(M){return A.fileInputEl=M},onChange:this.onDrop};T&&T.length&&(y.name=T);var u=["acceptedFiles","disablePreview","disableClick","onDropAccepted","onDropRejected","onFileDialogCancel","maxSize","minSize"],w=E({},Q);return u.forEach(function(A){return delete w[A]}),n.default.createElement("div",E({className:c,style:j},w,{onClick:this.onClick,onDragStart:this.onDragStart,onDragEnter:this.onDragEnter,onDragOver:this.onDragOver,onDragLeave:this.onDragLeave,onDrop:this.onDrop}),this.props.children,n.default.createElement("input",E({},e,y)))}}]),M}(n.default.Component);Q.defaultProps={disablePreview:!1,disableClick:!1,multiple:!0,maxSize:1/0,minSize:0},Q.propTypes={onClick:n.default.PropTypes.func,onDrop:n.default.PropTypes.func,onDropAccepted:n.default.PropTypes.func,onDropRejected:n.default.PropTypes.func,onDragStart:n.default.PropTypes.func,onDragEnter:n.default.PropTypes.func,onDragOver:n.default.PropTypes.func,onDragLeave:n.default.PropTypes.func,children:n.default.PropTypes.node,style:n.default.PropTypes.object,activeStyle:n.default.PropTypes.object,rejectStyle:n.default.PropTypes.object,className:n.default.PropTypes.string,activeClassName:n.default.PropTypes.string,rejectClassName:n.default.PropTypes.string,disablePreview:n.default.PropTypes.bool,disableClick:n.default.PropTypes.bool,onFileDialogCancel:n.default.PropTypes.func,inputProps:n.default.PropTypes.object,multiple:n.default.PropTypes.bool,accept:n.default.PropTypes.string,name:n.default.PropTypes.string,maxSize:n.default.PropTypes.number,minSize:n.default.PropTypes.number},M.default=Q,A.exports=M.default},function(M,t){M.exports=A},function(A,M){A.exports=function(A){function M(I){if(t[I])return t[I].exports;var g=t[I]={exports:{},id:I,loaded:!1};return A[I].call(g.exports,g,g.exports,M),g.loaded=!0,g.exports}var t={};return M.m=A,M.c=t,M.p="",M(0)}([function(A,M,t){"use strict";M.__esModule=!0,t(8),t(9),M.default=function(A,M){if(A&&M){var t=function(){var t=Array.isArray(M)?M:M.split(","),I=A.name||"",g=A.type||"",e=g.replace(/\/.*$/,"");return{v:t.some(function(A){var M=A.trim();return"."===M.charAt(0)?I.toLowerCase().endsWith(M.toLowerCase()):/\/\*$/.test(M)?e===M.replace(/\/.*$/,""):g===M})}}();if("object"==typeof t)return t.v}return!0},A.exports=M.default},function(A,M){var t=A.exports={version:"1.2.2"};"number"==typeof __e&&(__e=t)},function(A,M){var t=A.exports="undefined"!=typeof window&&window.Math==Math?window:"undefined"!=typeof self&&self.Math==Math?self:Function("return this")();"number"==typeof __g&&(__g=t)},function(A,M,t){var I=t(2),g=t(1),e=t(4),i=t(19),T="prototype",E=function(A,M){return function(){return A.apply(M,arguments)}},N=function(A,M,t){var o,n,C,c,D=A&N.G,a=A&N.P,B=D?I:A&N.S?I[M]||(I[M]={}):(I[M]||{})[T],Q=D?g:g[M]||(g[M]={});D&&(t=M);for(o in t)n=!(A&N.F)&&B&&o in B,C=(n?B:t)[o],c=A&N.B&&n?E(C,I):a&&"function"==typeof C?E(Function.call,C):C,B&&!n&&i(B,o,C),Q[o]!=C&&e(Q,o,c),a&&((Q[T]||(Q[T]={}))[o]=C)};I.core=g,N.F=1,N.G=2,N.S=4,N.P=8,N.B=16,N.W=32,A.exports=N},function(A,M,t){var I=t(5),g=t(18);A.exports=t(22)?function(A,M,t){return I.setDesc(A,M,g(1,t))}:function(A,M,t){return A[M]=t,A}},function(A,M){var t=Object;A.exports={create:t.create,getProto:t.getPrototypeOf,isEnum:{}.propertyIsEnumerable,getDesc:t.getOwnPropertyDescriptor,setDesc:t.defineProperty,setDescs:t.defineProperties,getKeys:t.keys,getNames:t.getOwnPropertyNames,getSymbols:t.getOwnPropertySymbols,each:[].forEach}},function(A,M){var t=0,I=Math.random();A.exports=function(A){return"Symbol(".concat(void 0===A?"":A,")_",(++t+I).toString(36))}},function(A,M,t){var I=t(20)("wks"),g=t(2).Symbol;A.exports=function(A){return I[A]||(I[A]=g&&g[A]||(g||t(6))("Symbol."+A))}},function(A,M,t){t(26),A.exports=t(1).Array.some},function(A,M,t){t(25),A.exports=t(1).String.endsWith},function(A,M){A.exports=function(A){if("function"!=typeof A)throw TypeError(A+" is not a function!");return A}},function(A,M){var t={}.toString;A.exports=function(A){return t.call(A).slice(8,-1)}},function(A,M,t){var I=t(10);A.exports=function(A,M,t){if(I(A),void 0===M)return A;switch(t){case 1:return function(t){return A.call(M,t)};case 2:return function(t,I){return A.call(M,t,I)};case 3:return function(t,I,g){return A.call(M,t,I,g)}}return function(){return A.apply(M,arguments)}}},function(A,M){A.exports=function(A){if(void 0==A)throw TypeError("Can't call method on "+A);return A}},function(A,M,t){A.exports=function(A){var M=/./;try{"/./"[A](M)}catch(I){try{return M[t(7)("match")]=!1,!"/./"[A](M)}catch(A){}}return!0}},function(A,M){A.exports=function(A){try{return!!A()}catch(A){return!0}}},function(A,M){A.exports=function(A){return"object"==typeof A?null!==A:"function"==typeof A}},function(A,M,t){var I=t(16),g=t(11),e=t(7)("match");A.exports=function(A){var M;return I(A)&&(void 0!==(M=A[e])?!!M:"RegExp"==g(A))}},function(A,M){A.exports=function(A,M){return{enumerable:!(1&A),configurable:!(2&A),writable:!(4&A),value:M}}},function(A,M,t){var I=t(2),g=t(4),e=t(6)("src"),i="toString",T=Function[i],E=(""+T).split(i);t(1).inspectSource=function(A){return T.call(A)},(A.exports=function(A,M,t,i){"function"==typeof t&&(g(t,e,A[M]?""+A[M]:E.join(String(M))),"name"in t||(t.name=M)),A===I?A[M]=t:(i||delete A[M],g(A,M,t))})(Function.prototype,i,function(){return"function"==typeof this&&this[e]||T.call(this)})},function(A,M,t){var I=t(2),g="__core-js_shared__",e=I[g]||(I[g]={});A.exports=function(A){return e[A]||(e[A]={})}},function(A,M,t){var I=t(17),g=t(13);A.exports=function(A,M,t){if(I(M))throw TypeError("String#"+t+" doesn't accept regex!");return String(g(A))}},function(A,M,t){A.exports=!t(15)(function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a})},function(A,M){var t=Math.ceil,I=Math.floor;A.exports=function(A){return isNaN(A=+A)?0:(A>0?I:t)(A)}},function(A,M,t){var I=t(23),g=Math.min;A.exports=function(A){return A>0?g(I(A),9007199254740991):0}},function(A,M,t){"use strict";var I=t(3),g=t(24),e=t(21),i="endsWith",T=""[i];I(I.P+I.F*t(14)(i),"String",{endsWith:function(A){var M=e(this,A,i),t=arguments,I=t.length>1?t[1]:void 0,E=g(M.length),N=void 0===I?E:Math.min(g(I),E),o=String(A);return T?T.call(M,o,N):M.slice(N-o.length,N)===o}})},function(A,M,t){var I=t(5),g=t(3),e=t(1).Array||Array,i={},T=function(A,M){I.each.call(A.split(","),function(A){void 0==M&&A in e?i[A]=e[A]:A in[]&&(i[A]=t(12)(Function.call,[][A],M))})};T("pop,reverse,shift,keys,values,entries",1),T("indexOf,every,some,forEach,map,filter,find,findIndex,includes",3),T("join,slice,concat,push,splice,unshift,sort,lastIndexOf,reduce,reduceRight,copyWithin,fill"),g(g.S,"Array",i)}])},function(A,M){"use strict";function t(A){var M=!(arguments.length>1&&void 0!==arguments[1])||arguments[1],t=[];if(A.dataTransfer){var I=A.dataTransfer;I.files&&I.files.length?t=I.files:I.items&&I.items.length&&(t=I.items)}else A.target&&A.target.files&&(t=A.target.files);return t.length>0&&(t=M?t:[t[0]]),Array.prototype.slice.call(t)}Object.defineProperty(M,"__esModule",{value:!0}),M.default=t,A.exports=M.default}])})},function(A,M,t){"use strict";function I(A){return A&&A.__esModule?A:{default:A}}function g(A,M){var t={};for(var I in A)M.indexOf(I)>=0||Object.prototype.hasOwnProperty.call(A,I)&&(t[I]=A[I]);return t}function e(A,M){if(!(A instanceof M))throw new TypeError("Cannot call a class as a function")}function i(A,M){if(!A)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!M||"object"!=typeof M&&"function"!=typeof M?A:M}function T(A,M){if("function"!=typeof M&&null!==M)throw new TypeError("Super expression must either be null or a function, not "+typeof M);A.prototype=Object.create(M&&M.prototype,{constructor:{value:A,enumerable:!1,writable:!0,configurable:!0}}),M&&(Object.setPrototypeOf?Object.setPrototypeOf(A,M):A.__proto__=M)}Object.defineProperty(M,"__esModule",{value:!0});var E=function(){function A(A,M){for(var t=0;t0&&void 0!==arguments[0]?arguments[0]:{},t=M.hideSiblingNodes,I=void 0===t||t,e=M.handleContainerOverflow,i=void 0===e||e;g(this,A),this.hideSiblingNodes=I,this.handleContainerOverflow=i,this.modals=[],this.containers=[],this.data=[]}return N(A,[{key:"add",value:function(A,M,t){var I=this.modals.indexOf(A),g=this.containers.indexOf(M);if(I!==-1)return I;if(I=this.modals.length,this.modals.push(A),this.hideSiblingNodes&&(0,r.hideSiblings)(M,A.mountNode),g!==-1)return this.data[g].modals.push(A),I;var e={modals:[A],classes:t?t.split(/\s+/):[],overflowing:(0,Q.default)(M)};return this.handleContainerOverflow&&T(e,M),e.classes.forEach(c.default.addClass.bind(null,M)),this.containers.push(M),this.data.push(e),I}},{key:"remove",value:function(A){var M=this.modals.indexOf(A);if(M!==-1){var t=i(this.data,A),I=this.data[t],g=this.containers[t];I.modals.splice(I.modals.indexOf(A),1),this.modals.splice(M,1),0===I.modals.length?(I.classes.forEach(c.default.removeClass.bind(null,g)),this.handleContainerOverflow&&E(I,g),this.hideSiblingNodes&&(0,r.showSiblings)(g,A.mountNode),this.containers.splice(t,1),this.data.splice(t,1)):this.hideSiblingNodes&&(0,r.ariaHidden)(!1,I.modals[I.modals.length-1].mountNode)}}},{key:"isTopModal",value:function(A){return!!this.modals.length&&this.modals[this.modals.length-1]===A}}]),A}();M.default=s,A.exports=M.default},function(A,M,t){"use strict";function I(A){return A&&A.__esModule?A:{default:A}}function g(A,M){var t={};for(var I in A)M.indexOf(I)>=0||Object.prototype.hasOwnProperty.call(A,I)&&(t[I]=A[I]);return t}function e(A,M){if(!(A instanceof M))throw new TypeError("Cannot call a class as a function")}function i(A,M){if(!A)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!M||"object"!=typeof M&&"function"!=typeof M?A:M}function T(A,M){if("function"!=typeof M&&null!==M)throw new TypeError("Super expression must either be null or a function, not "+typeof M);A.prototype=Object.create(M&&M.prototype,{constructor:{value:A,enumerable:!1,writable:!0,configurable:!0}}),M&&(Object.setPrototypeOf?Object.setPrototypeOf(A,M):A.__proto__=M)}Object.defineProperty(M,"__esModule",{value:!0});var E=Object.assign||function(A){for(var M=1;M1?t-1:0),g=1;g=0||Object.prototype.hasOwnProperty.call(A,I)&&(t[I]=A[I]);return t}function e(A,M){if(!(A instanceof M))throw new TypeError("Cannot call a class as a function")}function i(A,M){if(!A)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!M||"object"!=typeof M&&"function"!=typeof M?A:M}function T(A,M){if("function"!=typeof M&&null!==M)throw new TypeError("Super expression must either be null or a function, not "+typeof M);A.prototype=Object.create(M&&M.prototype,{constructor:{value:A,enumerable:!1,writable:!0,configurable:!0}}),M&&(Object.setPrototypeOf?Object.setPrototypeOf(A,M):A.__proto__=M)}Object.defineProperty(M,"__esModule",{value:!0});var E=Object.assign||function(A){for(var M=1;M=0||Object.prototype.hasOwnProperty.call(A,I)&&(t[I]=A[I]);return t}function e(A,M){if(!(A instanceof M))throw new TypeError("Cannot call a class as a function")}function i(A,M){if(!A)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!M||"object"!=typeof M&&"function"!=typeof M?A:M}function T(A,M){if("function"!=typeof M&&null!==M)throw new TypeError("Super expression must either be null or a function, not "+typeof M);A.prototype=Object.create(M&&M.prototype,{constructor:{value:A,enumerable:!1,writable:!0,configurable:!0}}),M&&(Object.setPrototypeOf?Object.setPrototypeOf(A,M):A.__proto__=M)}function E(){}Object.defineProperty(M,"__esModule",{value:!0}),M.EXITING=M.ENTERED=M.ENTERING=M.EXITED=M.UNMOUNTED=void 0;var N=Object.assign||function(A){for(var M=1;MT?T-N:0}function i(A,M,t,I){var e=g(t),i=e.width,T=A-I,E=A+I+M;return T<0?-T:E>i?i-E:0}function T(A,M,t,I,g){var T="BODY"===I.tagName?(0,N.default)(t):(0,n.default)(t,I),E=(0,N.default)(M),o=E.height,C=E.width,c=void 0,D=void 0,a=void 0,B=void 0;if("left"===A||"right"===A){D=T.top+(T.height-o)/2,c="left"===A?T.left-C:T.left+T.width;var Q=e(D,o,I,g);D+=Q,B=50*(1-2*Q/o)+"%",a=void 0}else{if("top"!==A&&"bottom"!==A)throw new Error('calcOverlayPosition(): No such placement of "'+A+'" found.');c=T.left+(T.width-C)/2,D="top"===A?T.top-o:T.top+T.height;var r=i(c,C,I,g);c+=r,a=50*(1-2*r/C)+"%",B=void 0}return{positionLeft:c,positionTop:D,arrowOffsetLeft:a,arrowOffsetTop:B}}Object.defineProperty(M,"__esModule",{value:!0}),M.default=T;var E=t(141),N=I(E),o=t(286),n=I(o),C=t(142),c=I(C),D=t(52),a=I(D);A.exports=M.default},function(A,M){"use strict";function t(A,M){M&&(A?M.setAttribute("aria-hidden","true"):M.removeAttribute("aria-hidden"))}function I(A,M){T(A,M,function(A){return t(!0,A)})}function g(A,M){T(A,M,function(A){return t(!1,A)})}Object.defineProperty(M,"__esModule",{value:!0}),M.ariaHidden=t,M.hideSiblings=I,M.showSiblings=g;var e=["template","script","style"],i=function(A){var M=A.nodeType,t=A.tagName;return 1===M&&e.indexOf(t.toLowerCase())===-1},T=function(A,M,t){M=[].concat(M),[].forEach.call(A.children,function(A){M.indexOf(A)===-1&&i(A)&&t(A)})}},function(A,M,t){"use strict";var I=function(){};A.exports=I},function(A,M,t){"use strict";function I(A){return A&&A.__esModule?A:{default:A}}function g(A,M){return function(t,I,g){if(null!=t[I]){var e='"'+I+'" property of "'+g+'" has been deprecated.\n'+M;E[e]||(T.default(!1,e),E[e]=!0)}return A(t,I,g)}}function e(){E={}}M.__esModule=!0,M.default=g;var i=t(127),T=I(i),E={};g._resetWarned=e,A.exports=M.default},function(A,M,t){"use strict";function I(A){return A&&A.__esModule?A:{default:A}}function g(A,M){if(!(A instanceof M))throw new TypeError("Cannot call a class as a function")}function e(A,M){if(!A)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!M||"object"!=typeof M&&"function"!=typeof M?A:M}function i(A,M){if("function"!=typeof M&&null!==M)throw new TypeError("Super expression must either be null or a function, not "+typeof M);A.prototype=Object.create(M&&M.prototype,{constructor:{value:A,enumerable:!1,writable:!0,configurable:!0}}),M&&(Object.setPrototypeOf?Object.setPrototypeOf(A,M):A.__proto__=M)}M.__esModule=!0,M.default=void 0;var T=t(1),E=t(182),N=I(E),o=t(183),n=(I(o),function(A){function M(t,I){g(this,M);var i=e(this,A.call(this,t,I));return i.store=t.store,i}return i(M,A),M.prototype.getChildContext=function(){return{store:this.store}},M.prototype.render=function(){return T.Children.only(this.props.children)},M}(T.Component));M.default=n,n.propTypes={store:N.default.isRequired,children:T.PropTypes.element.isRequired},n.childContextTypes={store:N.default.isRequired}},function(A,M){"use strict";function t(A,M){if(A===M)return!0;var t=Object.keys(A),I=Object.keys(M);if(t.length!==I.length)return!1;for(var g=Object.prototype.hasOwnProperty,e=0;e=0||Object.prototype.hasOwnProperty.call(A,I)&&(t[I]=A[I]);return t}function e(A,M){var t=A.history,I=A.routes,e=A.location,E=g(A,["history","routes","location"]);t||e?void 0:(0,N.default)(!1),t=t?t:(0,n.default)(E);var o=(0,c.default)(t,(0,D.createRoutes)(I)),C=void 0;e?e=t.createLocation(e):C=t.listen(function(A){e=A});var B=(0,a.createRouterObject)(t,o);t=(0,a.createRoutingHistory)(t,o),o.match(e,function(A,I,g){M(A,I&&B.createLocation(I,T.REPLACE),g&&i({},g,{history:t,router:B,matchContext:{history:t,transitionManager:o,router:B}})),C&&C()})}M.__esModule=!0;var i=Object.assign||function(A){for(var M=1;M=0||Object.prototype.hasOwnProperty.call(A,I)&&(t[I]=A[I]);return t}function e(A){return function(){var M=arguments.length<=0||void 0===arguments[0]?{}:arguments[0],t=M.routes,I=g(M,["routes"]),e=(0,E.default)(A)(I),T=(0,o.default)(e,t);return i({},e,T)}}M.__esModule=!0;var i=Object.assign||function(A){for(var M=1;M=A&&N&&(T=!0,t()))}}var i=0,T=!1,E=!1,N=!1,o=void 0;e()}M.__esModule=!0;var I=Array.prototype.slice;M.loopAsync=t},function(A,M,t){"use strict";function I(A){return A&&A.__esModule?A:{default:A}}function g(){function A(A){try{A=A||window.history.state||{}}catch(M){A={}}var M=n.getWindowPath(),t=A,I=t.key,g=void 0;I?g=C.readState(I):(g=null,I=s.createKey(),Q&&window.history.replaceState(e({},A,{key:I}),null));var i=N.parsePath(M);return s.createLocation(e({},i,{state:g}),void 0,I)}function M(M){function t(M){void 0!==M.state&&I(A(M.state))}var I=M.transitionTo;return n.addEventListener(window,"popstate",t),function(){n.removeEventListener(window,"popstate",t)}}function t(A){var M=A.basename,t=A.pathname,I=A.search,g=A.hash,e=A.state,i=A.action,T=A.key;if(i!==E.POP){C.saveState(T,e);var N=(M||"")+t+I+g,o={key:T};if(i===E.PUSH){if(r)return window.location.href=N,!1;window.history.pushState(o,null,N)}else{if(r)return window.location.replace(N),!1;window.history.replaceState(o,null,N)}}}function I(A){1===++x&&(j=M(s));var t=s.listenBefore(A);return function(){t(),0===--x&&j()}}function g(A){1===++x&&(j=M(s));var t=s.listen(A);return function(){t(),0===--x&&j()}}function i(A){1===++x&&(j=M(s)),s.registerTransitionHook(A)}function c(A){s.unregisterTransitionHook(A),0===--x&&j()}var a=arguments.length<=0||void 0===arguments[0]?{}:arguments[0];o.canUseDOM?void 0:T.default(!1);var B=a.forceRefresh,Q=n.supportsHistory(),r=!Q||B,s=D.default(e({},a,{getCurrentLocation:A,finishTransition:t,saveState:C.saveState})),x=0,j=void 0;return e({},s,{listenBefore:I,listen:g,registerTransitionHook:i,unregisterTransitionHook:c})}M.__esModule=!0;var e=Object.assign||function(A){for(var M=1;M=0&&M=0&&B8&&u<=11),L=32,Y=String.fromCharCode(L),d=c.topLevelTypes,h={beforeInput:{phasedRegistrationNames:{bubbled:s({onBeforeInput:null}),captured:s({onBeforeInputCapture:null})},dependencies:[d.topCompositionEnd,d.topKeyPress,d.topTextInput,d.topPaste]},compositionEnd:{phasedRegistrationNames:{bubbled:s({onCompositionEnd:null}),captured:s({onCompositionEndCapture:null})},dependencies:[d.topBlur,d.topCompositionEnd,d.topKeyDown,d.topKeyPress,d.topKeyUp,d.topMouseDown]},compositionStart:{phasedRegistrationNames:{bubbled:s({onCompositionStart:null}),captured:s({onCompositionStartCapture:null})},dependencies:[d.topBlur,d.topCompositionStart,d.topKeyDown,d.topKeyPress,d.topKeyUp,d.topMouseDown]},compositionUpdate:{phasedRegistrationNames:{bubbled:s({onCompositionUpdate:null}),captured:s({onCompositionUpdateCapture:null})},dependencies:[d.topBlur,d.topCompositionUpdate,d.topKeyDown,d.topKeyPress,d.topKeyUp,d.topMouseDown]}},S=!1,z=null,p={eventTypes:h,extractEvents:function(A,M,t,I,g){return[N(A,M,t,I,g),C(A,M,t,I,g)]}};A.exports=p},function(A,M,t){"use strict";var I=t(200),g=t(10),e=t(17),i=(t(296),t(475)),T=t(301),E=t(305),N=(t(3),E(function(A){return T(A)})),o=!1,n="cssFloat";if(g.canUseDOM){var C=document.createElement("div").style;try{C.font=""}catch(A){o=!0}void 0===document.documentElement.style.cssFloat&&(n="styleFloat")}var c={createMarkupForStyles:function(A){var M="";for(var t in A)if(A.hasOwnProperty(t)){var I=A[t];null!=I&&(M+=N(t)+":",M+=i(t,I)+";")}return M||null},setValueForStyles:function(A,M){var t=A.style;for(var g in M)if(M.hasOwnProperty(g)){var e=i(g,M[g]);if("float"===g&&(g=n),e)t[g]=e;else{var T=o&&I.shorthandPropertyExpansions[g];if(T)for(var E in T)t[E]="";else t[g]=""}}}};e.measureMethods(c,"CSSPropertyOperations",{setValueForStyles:"setValueForStyles"}),A.exports=c},function(A,M,t){"use strict";function I(A){var M=A.nodeName&&A.nodeName.toLowerCase();return"select"===M||"input"===M&&"file"===A.type}function g(A){var M=u.getPooled(h.change,z,A,w(A));x.accumulateTwoPhaseDispatches(M),y.batchedUpdates(e,M)}function e(A){s.enqueueEvents(A),s.processEventQueue(!1)}function i(A,M){S=A,z=M,S.attachEvent("onchange",g)}function T(){S&&(S.detachEvent("onchange",g),S=null,z=null)}function E(A,M,t){if(A===d.topChange)return t}function N(A,M,t){A===d.topFocus?(T(),i(M,t)):A===d.topBlur&&T()}function o(A,M){S=A,z=M,p=A.value,U=Object.getOwnPropertyDescriptor(A.constructor.prototype,"value"),Object.defineProperty(S,"value",F),S.attachEvent("onpropertychange",C)}function n(){S&&(delete S.value,S.detachEvent("onpropertychange",C),S=null,z=null,p=null,U=null)}function C(A){if("value"===A.propertyName){var M=A.srcElement.value;M!==p&&(p=M,g(A))}}function c(A,M,t){if(A===d.topInput)return t}function D(A,M,t){A===d.topFocus?(n(),o(M,t)):A===d.topBlur&&n()}function a(A,M,t){if((A===d.topSelectionChange||A===d.topKeyUp||A===d.topKeyDown)&&S&&S.value!==p)return p=S.value,z}function B(A){return A.nodeName&&"input"===A.nodeName.toLowerCase()&&("checkbox"===A.type||"radio"===A.type)}function Q(A,M,t){if(A===d.topClick)return t}var r=t(24),s=t(53),x=t(54),j=t(10),y=t(18),u=t(37),w=t(117),l=t(120),L=t(227),Y=t(27),d=r.topLevelTypes,h={change:{phasedRegistrationNames:{bubbled:Y({onChange:null}),captured:Y({onChangeCapture:null})},dependencies:[d.topBlur,d.topChange,d.topClick,d.topFocus,d.topInput,d.topKeyDown,d.topKeyUp,d.topSelectionChange]}},S=null,z=null,p=null,U=null,O=!1;j.canUseDOM&&(O=l("change")&&(!("documentMode"in document)||document.documentMode>8));var m=!1;j.canUseDOM&&(m=l("input")&&(!("documentMode"in document)||document.documentMode>9));var F={get:function(){return U.get.call(this)},set:function(A){p=""+A,U.set.call(this,A)}},f={eventTypes:h,extractEvents:function(A,M,t,g,e){var i,T;if(I(M)?O?i=E:T=N:L(M)?m?i=c:(i=a,T=D):B(M)&&(i=Q),i){var o=i(A,M,t);if(o){var n=u.getPooled(h.change,o,g,e);return n.type="change",x.accumulateTwoPhaseDispatches(n),n}}T&&T(A,M,t)}};A.exports=f},function(A,M){"use strict";var t=0,I={createReactRootIndex:function(){return t++}};A.exports=I},function(A,M,t){"use strict";function I(A){return A.substring(1,A.indexOf(" "))}var g=t(10),e=t(298),i=t(20),T=t(149),E=t(2),N=/^(<[^ \/>]+)/,o="data-danger-index",n={dangerouslyRenderMarkup:function(A){g.canUseDOM?void 0:E(!1);for(var M,t={},n=0;n1?1-M:void 0;return this._fallbackText=g.slice(A,T),this._fallbackText}}),g.addPoolingTo(I),A.exports=I},function(A,M,t){"use strict";var I,g=t(47),e=t(10),i=g.injection.MUST_USE_ATTRIBUTE,T=g.injection.MUST_USE_PROPERTY,E=g.injection.HAS_BOOLEAN_VALUE,N=g.injection.HAS_SIDE_EFFECTS,o=g.injection.HAS_NUMERIC_VALUE,n=g.injection.HAS_POSITIVE_NUMERIC_VALUE,C=g.injection.HAS_OVERLOADED_BOOLEAN_VALUE;if(e.canUseDOM){var c=document.implementation;I=c&&c.hasFeature&&c.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure","1.1")}var D={isCustomAttribute:RegExp.prototype.test.bind(/^(data|aria)-[a-z_][a-z\d_.\-]*$/),Properties:{accept:null,acceptCharset:null,accessKey:null,action:null,allowFullScreen:i|E,allowTransparency:i,alt:null,async:E,autoComplete:null,autoPlay:E,capture:i|E,cellPadding:null,cellSpacing:null,charSet:i,challenge:i,checked:T|E,classID:i,className:I?i:T,cols:i|n,colSpan:null,content:null,contentEditable:null,contextMenu:i,controls:T|E,coords:null,crossOrigin:null,data:null,dateTime:i,default:E,defer:E,dir:null,disabled:i|E,download:C,draggable:null,encType:null,form:i,formAction:i,formEncType:i,formMethod:i,formNoValidate:E,formTarget:i,frameBorder:i,headers:null,height:i,hidden:i|E,high:null,href:null,hrefLang:null,htmlFor:null,httpEquiv:null,icon:null,id:T,inputMode:i,integrity:null,is:i,keyParams:i,keyType:i,kind:null,label:null,lang:null,list:i,loop:T|E,low:null,manifest:i,marginHeight:null,marginWidth:null,max:null,maxLength:i,media:i,mediaGroup:null,method:null,min:null,minLength:i,multiple:T|E,muted:T|E,name:null,nonce:i,noValidate:E,open:E,optimum:null,pattern:null,placeholder:null,poster:null,preload:null,radioGroup:null,readOnly:T|E,rel:null,required:E,reversed:E,role:i,rows:i|n,rowSpan:null,sandbox:null,scope:null,scoped:E,scrolling:null,seamless:i|E,selected:T|E,shape:null,size:i|n,sizes:i,span:n,spellCheck:null,src:null,srcDoc:T,srcLang:null,srcSet:i,start:o,step:null,style:null,summary:null,tabIndex:null,target:null,title:null,type:null,useMap:null,value:T|N,width:i,wmode:i,wrap:null,about:i,datatype:i,inlist:i,prefix:i,property:i,resource:i,typeof:i,vocab:i,autoCapitalize:i,autoCorrect:i,autoSave:null,color:null,itemProp:i,itemScope:i|E,itemType:i,itemID:i,itemRef:i,results:null,security:i,unselectable:i},DOMAttributeNames:{acceptCharset:"accept-charset",className:"class",htmlFor:"for",httpEquiv:"http-equiv"},DOMPropertyNames:{autoComplete:"autocomplete",autoFocus:"autofocus",autoPlay:"autoplay",autoSave:"autosave",encType:"encoding",hrefLang:"hreflang",radioGroup:"radiogroup",spellCheck:"spellcheck",srcDoc:"srcdoc",srcSet:"srcset"}};A.exports=D},function(A,M,t){"use strict";var I=t(206),g=t(449),e=t(454),i=t(4),T=t(476),E={};i(E,e),i(E,{findDOMNode:T("findDOMNode","ReactDOM","react-dom",I,I.findDOMNode),render:T("render","ReactDOM","react-dom",I,I.render),unmountComponentAtNode:T("unmountComponentAtNode","ReactDOM","react-dom",I,I.unmountComponentAtNode),renderToString:T("renderToString","ReactDOMServer","react-dom/server",g,g.renderToString),renderToStaticMarkup:T("renderToStaticMarkup","ReactDOMServer","react-dom/server",g,g.renderToStaticMarkup)}),E.__SECRET_DOM_DO_NOT_USE_OR_YOU_WILL_BE_FIRED=I,E.__SECRET_DOM_SERVER_DO_NOT_USE_OR_YOU_WILL_BE_FIRED=g,A.exports=E},function(A,M,t){"use strict";var I=(t(55),t(114)),g=(t(3),"_getDOMNodeDidWarn"),e={getDOMNode:function(){return this.constructor[g]=!0,I(this)}};A.exports=e},function(A,M,t){"use strict";function I(A,M,t){var I=void 0===A[t];null!=M&&I&&(A[t]=e(M,null))}var g=t(36),e=t(119),i=t(122),T=t(123),E=(t(3),{instantiateChildren:function(A,M,t){if(null==A)return null;var g={};return T(A,I,g),g},updateChildren:function(A,M,t,I){if(!M&&!A)return null;var T;for(T in M)if(M.hasOwnProperty(T)){var E=A&&A[T],N=E&&E._currentElement,o=M[T];if(null!=E&&i(N,o))g.receiveComponent(E,o,t,I),M[T]=E;else{E&&g.unmountComponent(E,T);var n=e(o,null);M[T]=n}}for(T in A)!A.hasOwnProperty(T)||M&&M.hasOwnProperty(T)||g.unmountComponent(A[T]);return M},unmountChildren:function(A){for(var M in A)if(A.hasOwnProperty(M)){var t=A[M];g.unmountComponent(t)}}});A.exports=E},function(A,M,t){"use strict";function I(A){var M=A._currentElement._owner||null;if(M){var t=M.getName();if(t)return" Check the render method of ` + "`" + `"+t+"` + "`" + `."}return""}function g(A){}var e=t(110),i=t(25),T=t(13),E=t(55),N=t(17),o=t(71),n=(t(70),t(36)),C=t(112),c=t(4),D=t(50),a=t(2),B=t(122);t(3);g.prototype.render=function(){var A=E.get(this)._currentElement.type;return A(this.props,this.context,this.updater)};var Q=1,r={construct:function(A){this._currentElement=A,this._rootNodeID=null,this._instance=null,this._pendingElement=null,this._pendingStateQueue=null,this._pendingReplaceState=!1,this._pendingForceUpdate=!1,this._renderedComponent=null,this._context=null,this._mountOrder=0,this._topLevelWrapper=null,this._pendingCallbacks=null},mountComponent:function(A,M,t){this._context=t,this._mountOrder=Q++,this._rootNodeID=A;var I,e,i=this._processProps(this._currentElement.props),N=this._processContext(t),o=this._currentElement.type,c="prototype"in o;c&&(I=new o(i,N,C)),c&&null!==I&&I!==!1&&!T.isValidElement(I)||(e=I,I=new g(o)),I.props=i,I.context=N,I.refs=D,I.updater=C,this._instance=I,E.set(I,this);var B=I.state;void 0===B&&(I.state=B=null),"object"!=typeof B||Array.isArray(B)?a(!1):void 0,this._pendingStateQueue=null,this._pendingReplaceState=!1,this._pendingForceUpdate=!1,I.componentWillMount&&(I.componentWillMount(),this._pendingStateQueue&&(I.state=this._processPendingState(I.props,I.context))),void 0===e&&(e=this._renderValidatedComponent()),this._renderedComponent=this._instantiateReactComponent(e);var r=n.mountComponent(this._renderedComponent,A,M,this._processChildContext(t));return I.componentDidMount&&M.getReactMountReady().enqueue(I.componentDidMount,I),r},unmountComponent:function(){var A=this._instance;A.componentWillUnmount&&A.componentWillUnmount(),n.unmountComponent(this._renderedComponent),this._renderedComponent=null,this._instance=null,this._pendingStateQueue=null,this._pendingReplaceState=!1,this._pendingForceUpdate=!1,this._pendingCallbacks=null,this._pendingElement=null,this._context=null,this._rootNodeID=null,this._topLevelWrapper=null,E.remove(A)},_maskContext:function(A){var M=null,t=this._currentElement.type,I=t.contextTypes;if(!I)return D;M={};for(var g in I)M[g]=A[g];return M},_processContext:function(A){var M=this._maskContext(A);return M},_processChildContext:function(A){var M=this._currentElement.type,t=this._instance,I=t.getChildContext&&t.getChildContext();if(I){"object"!=typeof M.childContextTypes?a(!1):void 0;for(var g in I)g in M.childContextTypes?void 0:a(!1);return c({},A,I)}return A},_processProps:function(A){return A},_checkPropTypes:function(A,M,t){var g=this.getName();for(var e in A)if(A.hasOwnProperty(e)){var i;try{"function"!=typeof A[e]?a(!1):void 0,i=A[e](M,e,g,t)}catch(A){i=A}if(i instanceof Error){I(this);t===o.prop}}},receiveComponent:function(A,M,t){var I=this._currentElement,g=this._context;this._pendingElement=null,this.updateComponent(M,I,A,g,t)},performUpdateIfNecessary:function(A){null!=this._pendingElement&&n.receiveComponent(this,this._pendingElement||this._currentElement,A,this._context),(null!==this._pendingStateQueue||this._pendingForceUpdate)&&this.updateComponent(A,this._currentElement,this._currentElement,this._context,this._context)},updateComponent:function(A,M,t,I,g){var e,i=this._instance,T=this._context===g?i.context:this._processContext(g);M===t?e=t.props:(e=this._processProps(t.props),i.componentWillReceiveProps&&i.componentWillReceiveProps(e,T));var E=this._processPendingState(e,T),N=this._pendingForceUpdate||!i.shouldComponentUpdate||i.shouldComponentUpdate(e,E,T);N?(this._pendingForceUpdate=!1,this._performComponentUpdate(t,e,E,T,A,g)):(this._currentElement=t,this._context=g,i.props=e,i.state=E,i.context=T)},_processPendingState:function(A,M){var t=this._instance,I=this._pendingStateQueue,g=this._pendingReplaceState;if(this._pendingReplaceState=!1,this._pendingStateQueue=null,!I)return t.state;if(g&&1===I.length)return I[0];for(var e=c({},g?I[0]:t.state),i=g?1:0;i=0||null!=M.is}function B(A){D(A),this._tag=A.toLowerCase(),this._renderedChildren=null,this._previousStyle=null,this._previousStyleCopy=null,this._rootNodeID=null,this._wrapperState=null,this._topLevelWrapper=null,this._nodeWithLegacyProperties=null}var Q=t(428),r=t(430),s=t(47),x=t(107),j=t(24),y=t(69),u=t(109),w=t(443),l=t(446),L=t(447),Y=t(208),d=t(450),h=t(12),S=t(455),z=t(17),p=t(112),U=t(4),O=t(74),m=t(75),F=t(2),f=(t(120),t(27)),k=t(76),R=t(121),J=(t(150),t(124),t(3),y.deleteListener),G=y.listenTo,H=y.registrationNameModules,b={string:!0,number:!0},v=f({children:null}),X=f({style:null}),W=f({__html:null}),V=1,P={topAbort:"abort",topCanPlay:"canplay",topCanPlayThrough:"canplaythrough",topDurationChange:"durationchange",topEmptied:"emptied",topEncrypted:"encrypted",topEnded:"ended",topError:"error",topLoadedData:"loadeddata",topLoadedMetadata:"loadedmetadata",topLoadStart:"loadstart",topPause:"pause",topPlay:"play",topPlaying:"playing",topProgress:"progress",topRateChange:"ratechange",topSeeked:"seeked",topSeeking:"seeking",topStalled:"stalled",topSuspend:"suspend",topTimeUpdate:"timeupdate",topVolumeChange:"volumechange",topWaiting:"waiting"},Z={area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0},K={listing:!0,pre:!0,textarea:!0},q=(U({menuitem:!0},Z),/^[a-zA-Z][a-zA-Z:_\.\-\d]*$/),_={},$={}.hasOwnProperty;B.displayName="ReactDOMComponent",B.Mixin={construct:function(A){this._currentElement=A},mountComponent:function(A,M,t){this._rootNodeID=A;var I=this._currentElement.props;switch(this._tag){case"iframe":case"img":case"form":case"video":case"audio":this._wrapperState={listeners:null},M.getReactMountReady().enqueue(n,this);break;case"button":I=w.getNativeProps(this,I,t);break;case"input":l.mountWrapper(this,I,t),I=l.getNativeProps(this,I,t);break;case"option":L.mountWrapper(this,I,t),I=L.getNativeProps(this,I,t);break;case"select":Y.mountWrapper(this,I,t),I=Y.getNativeProps(this,I,t),t=Y.processChildContext(this,I,t);break;case"textarea":d.mountWrapper(this,I,t),I=d.getNativeProps(this,I,t)}E(this,I);var g;if(M.useCreateElement){var e=t[h.ownerDocumentContextKey],i=e.createElement(this._currentElement.type);x.setAttributeForID(i,this._rootNodeID),h.getID(i),this._updateDOMProperties({},I,M,i),this._createInitialChildren(M,I,t,i),g=i}else{var T=this._createOpenTagMarkupAndPutListeners(M,I),N=this._createContentMarkup(M,I,t);g=!N&&Z[this._tag]?T+"/>":T+">"+N+""}switch(this._tag){case"input":M.getReactMountReady().enqueue(C,this);case"button":case"select":case"textarea":I.autoFocus&&M.getReactMountReady().enqueue(Q.focusDOMComponent,this)}return g},_createOpenTagMarkupAndPutListeners:function(A,M){var t="<"+this._currentElement.type;for(var I in M)if(M.hasOwnProperty(I)){var g=M[I];if(null!=g)if(H.hasOwnProperty(I))g&&N(this._rootNodeID,I,g,A);else{I===X&&(g&&(g=this._previousStyleCopy=U({},M.style)),g=r.createMarkupForStyles(g));var e=null;null!=this._tag&&a(this._tag,M)?I!==v&&(e=x.createMarkupForCustomAttribute(I,g)):e=x.createMarkupForProperty(I,g),e&&(t+=" "+e)}}if(A.renderToStaticMarkup)return t;var i=x.createMarkupForID(this._rootNodeID);return t+" "+i},_createContentMarkup:function(A,M,t){var I="",g=M.dangerouslySetInnerHTML;if(null!=g)null!=g.__html&&(I=g.__html);else{var e=b[typeof M.children]?M.children:null,i=null!=e?null:M.children;if(null!=e)I=m(e);else if(null!=i){var T=this.mountChildren(i,A,t);I=T.join("")}}return K[this._tag]&&"\n"===I.charAt(0)?"\n"+I:I},_createInitialChildren:function(A,M,t,I){var g=M.dangerouslySetInnerHTML;if(null!=g)null!=g.__html&&k(I,g.__html);else{var e=b[typeof M.children]?M.children:null,i=null!=e?null:M.children;if(null!=e)R(I,e);else if(null!=i)for(var T=this.mountChildren(i,A,t),E=0;EM.end?(t=M.end,I=M.start):(t=M.start,I=M.end),g.moveToElementText(A),g.moveStart("character",t),g.setEndPoint("EndToStart",g),g.moveEnd("character",I-t),g.select()}function T(A,M){if(window.getSelection){var t=window.getSelection(),I=A[o()].length,g=Math.min(M.start,I),e="undefined"==typeof M.end?g:Math.min(M.end,I);if(!t.extend&&g>e){var i=e;e=g,g=i}var T=N(A,g),E=N(A,e);if(T&&E){var n=document.createRange();n.setStart(T.node,T.offset),t.removeAllRanges(),g>e?(t.addRange(n),t.extend(E.node,E.offset)):(n.setEnd(E.node,E.offset),t.addRange(n))}}}var E=t(10),N=t(479),o=t(226),n=E.canUseDOM&&"selection"in document&&!("getSelection"in window),C={getOffsets:n?g:e,setOffsets:n?i:T};A.exports=C},function(A,M,t){"use strict";var I=t(211),g=t(460),e=t(113);I.inject();var i={renderToString:g.renderToString,renderToStaticMarkup:g.renderToStaticMarkup,version:e};A.exports=i},function(A,M,t){"use strict";function I(){this._rootNodeID&&o.updateWrapper(this)}function g(A){var M=this._currentElement.props,t=e.executeOnChange(M,A);return T.asap(I,this),t}var e=t(108),i=t(111),T=t(18),E=t(4),N=t(2),o=(t(3),{getNativeProps:function(A,M,t){null!=M.dangerouslySetInnerHTML?N(!1):void 0;var I=E({},M,{defaultValue:void 0,value:void 0,children:A._wrapperState.initialValue,onChange:A._wrapperState.onChange});return I},mountWrapper:function(A,M){var t=M.defaultValue,I=M.children;null!=I&&(null!=t?N(!1):void 0,Array.isArray(I)&&(I.length<=1?void 0:N(!1),I=I[0]),t=""+I),null==t&&(t="");var i=e.getValue(M);A._wrapperState={initialValue:""+(null!=i?i:t),onChange:g.bind(A)}},updateWrapper:function(A){var M=A._currentElement.props,t=e.getValue(M);null!=t&&i.updatePropertyByID(A._rootNodeID,"value",""+t)}});A.exports=o},function(A,M,t){"use strict";function I(A){g.enqueueEvents(A),g.processEventQueue(!1)}var g=t(53),e={handleTopLevel:function(A,M,t,e,i){var T=g.extractEvents(A,M,t,e,i);I(T)}};A.exports=e},function(A,M,t){"use strict";function I(A){var M=C.getID(A),t=n.getReactRootIDFromNodeID(M),I=C.findReactContainerForID(t),g=C.getFirstReactDOM(I);return g}function g(A,M){this.topLevelType=A,this.nativeEvent=M,this.ancestors=[]}function e(A){i(A)}function i(A){for(var M=C.getFirstReactDOM(a(A.nativeEvent))||window,t=M;t;)A.ancestors.push(t),t=I(t);for(var g=0;g=M)return{node:g,offset:M-e};e=i}g=t(I(g))}}A.exports=g},function(A,M,t){"use strict";function I(A){return g.isValidElement(A)?void 0:e(!1),A}var g=t(13),e=t(2);A.exports=I},function(A,M,t){"use strict";function I(A){return'"'+g(A)+'"'}var g=t(75);A.exports=I},function(A,M,t){"use strict";var I=t(12);A.exports=I.renderSubtreeIntoContainer},function(A,M){A.exports=function(A,M,t){for(var I=0,g=A.length,e=3==arguments.length?t:A[I++];I=400){ +var T="cannot "+M.method+" "+M.url+" ("+i.status+")";A=new e(T),A.status=i.status,A.body=i.body,A.res=i,I(A)}else g?I(new e(g)):t(i)})})},g.prototype.then=function(){var A=this.promise();return A.then.apply(A,arguments)}},function(A,M,t){function I(){}function g(A){var M={}.toString.call(A);switch(M){case"[object File]":case"[object Blob]":case"[object FormData]":return!0;default:return!1}}function e(A){if(!s(A))return A;var M=[];for(var t in A)null!=A[t]&&i(M,t,A[t]);return M.join("&")}function i(A,M,t){return Array.isArray(t)?t.forEach(function(t){i(A,M,t)}):void A.push(encodeURIComponent(M)+"="+encodeURIComponent(t))}function T(A){for(var M,t,I={},g=A.split("&"),e=0,i=g.length;e=200&&M.status<300)return t.callback(A,M);var I=new Error(M.statusText||"Unsuccessful HTTP response");I.original=A,I.response=M,I.status=M.status,t.callback(I,M)})}function D(A,M){var t=x("DELETE",A);return M&&t.end(M),t}var a,B=t(272),Q=t(483),r=t(501),s=t(234);a="undefined"!=typeof window?window:"undefined"!=typeof self?self:this;var x=A.exports=t(502).bind(null,c);x.getXHR=function(){if(!(!a.XMLHttpRequest||a.location&&"file:"==a.location.protocol&&a.ActiveXObject))return new XMLHttpRequest;try{return new ActiveXObject("Microsoft.XMLHTTP")}catch(A){}try{return new ActiveXObject("Msxml2.XMLHTTP.6.0")}catch(A){}try{return new ActiveXObject("Msxml2.XMLHTTP.3.0")}catch(A){}try{return new ActiveXObject("Msxml2.XMLHTTP")}catch(A){}return!1};var j="".trim?function(A){return A.trim()}:function(A){return A.replace(/(^\s*|\s*$)/g,"")};x.serializeObject=e,x.parseString=T,x.types={html:"text/html",json:"application/json",xml:"application/xml",urlencoded:"application/x-www-form-urlencoded",form:"application/x-www-form-urlencoded","form-data":"application/x-www-form-urlencoded"},x.serialize={"application/x-www-form-urlencoded":e,"application/json":JSON.stringify},x.parse={"application/x-www-form-urlencoded":T,"application/json":JSON.parse},C.prototype.get=function(A){return this.header[A.toLowerCase()]},C.prototype.setHeaderProperties=function(A){var M=this.header["content-type"]||"";this.type=o(M);var t=n(M);for(var I in t)this[I]=t[I]},C.prototype.parseBody=function(A){var M=x.parse[this.type];return!M&&N(this.type)&&(M=x.parse["application/json"]),M&&A&&(A.length||A instanceof Object)?M(A):null},C.prototype.setStatusProperties=function(A){1223===A&&(A=204);var M=A/100|0;this.status=this.statusCode=A,this.statusType=M,this.info=1==M,this.ok=2==M,this.clientError=4==M,this.serverError=5==M,this.error=(4==M||5==M)&&this.toError(),this.accepted=202==A,this.noContent=204==A,this.badRequest=400==A,this.unauthorized=401==A,this.notAcceptable=406==A,this.notFound=404==A,this.forbidden=403==A},C.prototype.toError=function(){var A=this.req,M=A.method,t=A.url,I="cannot "+M+" "+t+" ("+this.status+")",g=new Error(I);return g.status=this.status,g.method=M,g.url=t,g},x.Response=C,B(c.prototype);for(var y in r)c.prototype[y]=r[y];c.prototype.abort=function(){if(!this.aborted)return this.aborted=!0,this.xhr&&this.xhr.abort(),this.clearTimeout(),this.emit("abort"),this},c.prototype.type=function(A){return this.set("Content-Type",x.types[A]||A),this},c.prototype.responseType=function(A){return this._responseType=A,this},c.prototype.accept=function(A){return this.set("Accept",x.types[A]||A),this},c.prototype.auth=function(A,M,t){switch(t||(t={type:"basic"}),t.type){case"basic":var I=btoa(A+":"+M);this.set("Authorization","Basic "+I);break;case"auto":this.username=A,this.password=M}return this},c.prototype.query=function(A){return"string"!=typeof A&&(A=e(A)),A&&this._query.push(A),this},c.prototype.attach=function(A,M,t){return this._getFormData().append(A,M,t||M.name),this},c.prototype._getFormData=function(){return this._formData||(this._formData=new a.FormData),this._formData},c.prototype.send=function(A){var M=s(A),t=this._header["content-type"];if(M&&s(this._data))for(var I in A)this._data[I]=A[I];else"string"==typeof A?(t||this.type("form"),t=this._header["content-type"],"application/x-www-form-urlencoded"==t?this._data=this._data?this._data+"&"+A:A:this._data=(this._data||"")+A):this._data=A;return!M||g(A)?this:(t||this.type("json"),this)},C.prototype.parse=function(A){return a.console&&console.warn("Client-side parse() method has been renamed to serialize(). This method is not compatible with superagent v2.0"),this.serialize(A),this},C.prototype.serialize=function(A){return this._parser=A,this},c.prototype.callback=function(A,M){var t=this._callback;this.clearTimeout(),t(A,M)},c.prototype.crossDomainError=function(){var A=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.");A.crossDomain=!0,A.status=this.status,A.method=this.method,A.url=this.url,this.callback(A)},c.prototype.timeoutError=function(){var A=this._timeout,M=new Error("timeout of "+A+"ms exceeded");M.timeout=A,this.callback(M)},c.prototype.withCredentials=function(){return this._withCredentials=!0,this},c.prototype.end=function(A){var M=this,t=this.xhr=x.getXHR(),e=this._query.join("&"),i=this._timeout,T=this._formData||this._data;this._callback=A||I,t.onreadystatechange=function(){if(4==t.readyState){var A;try{A=t.status}catch(M){A=0}if(0==A){if(M.timedout)return M.timeoutError();if(M.aborted)return;return M.crossDomainError()}M.emit("end")}};var E=function(A){A.total>0&&(A.percent=A.loaded/A.total*100),A.direction="download",M.emit("progress",A)};this.hasListeners("progress")&&(t.onprogress=E);try{t.upload&&this.hasListeners("progress")&&(t.upload.onprogress=E)}catch(A){}if(i&&!this._timer&&(this._timer=setTimeout(function(){M.timedout=!0,M.abort()},i)),e&&(e=x.serializeObject(e),this.url+=~this.url.indexOf("?")?"&"+e:"?"+e),this.username&&this.password?t.open(this.method,this.url,!0,this.username,this.password):t.open(this.method,this.url,!0),this._withCredentials&&(t.withCredentials=!0),"GET"!=this.method&&"HEAD"!=this.method&&"string"!=typeof T&&!g(T)){var o=this._header["content-type"],n=this._parser||x.serialize[o?o.split(";")[0]:""];!n&&N(o)&&(n=x.serialize["application/json"]),n&&(T=n(T))}for(var C in this.header)null!=this.header[C]&&t.setRequestHeader(C,this.header[C]);return this._responseType&&(t.responseType=this._responseType),this.emit("request",this),t.send("undefined"!=typeof T?T:null),this},x.Request=c,x.get=function(A,M,t){var I=x("GET",A);return"function"==typeof M&&(t=M,M=null),M&&I.query(M),t&&I.end(t),I},x.head=function(A,M,t){var I=x("HEAD",A);return"function"==typeof M&&(t=M,M=null),M&&I.send(M),t&&I.end(t),I},x.del=D,x.delete=D,x.patch=function(A,M,t){var I=x("PATCH",A);return"function"==typeof M&&(t=M,M=null),M&&I.send(M),t&&I.end(t),I},x.post=function(A,M,t){var I=x("POST",A);return"function"==typeof M&&(t=M,M=null),M&&I.send(M),t&&I.end(t),I},x.put=function(A,M,t){var I=x("PUT",A);return"function"==typeof M&&(t=M,M=null),M&&I.send(M),t&&I.end(t),I}},function(A,M,t){var I=t(234);M.clearTimeout=function(){return this._timeout=0,clearTimeout(this._timer),this},M.parse=function(A){return this._parser=A,this},M.timeout=function(A){return this._timeout=A,this},M.then=function(A,M){return this.end(function(t,I){t?M(t):A(I)})},M.use=function(A){return A(this),this},M.get=function(A){return this._header[A.toLowerCase()]},M.getHeader=M.get,M.set=function(A,M){if(I(A)){for(var t in A)this.set(t,A[t]);return this}return this._header[A.toLowerCase()]=M,this.header[A]=M,this},M.unset=function(A){return delete this._header[A.toLowerCase()],delete this.header[A],this},M.field=function(A,M){return this._getFormData().append(A,M),this}},function(A,M){function t(A,M,t){return"function"==typeof t?new A("GET",M).end(t):2==arguments.length?new A("GET",M):new A(M,t)}A.exports=t},function(A,M,t){A.exports=t(504)},function(A,M,t){(function(A,I){"use strict";function g(A){return A&&A.__esModule?A:{default:A}}Object.defineProperty(M,"__esModule",{value:!0});var e,i=t(505),T=g(i);e="undefined"!=typeof self?self:"undefined"!=typeof window?window:"undefined"!=typeof A?A:I;var E=(0,T.default)(e);M.default=E}).call(M,function(){return this}(),t(128)(A))},function(A,M){"use strict";function t(A){var M,t=A.Symbol;return"function"==typeof t?t.observable?M=t.observable:(M=t("observable"),t.observable=M):M="@@observable",M}Object.defineProperty(M,"__esModule",{value:!0}),M.default=t},function(A,M,t){function I(A){return g(A).replace(/\s(\w)/g,function(A,M){return M.toUpperCase()})}var g=t(508);A.exports=I},function(A,M){function t(A){return e.test(A)?A.toLowerCase():i.test(A)?(I(A)||A).toLowerCase():T.test(A)?g(A).toLowerCase():A.toLowerCase()}function I(A){return A.replace(E,function(A,M){return M?" "+M:""})}function g(A){return A.replace(N,function(A,M,t){return M+" "+t.toLowerCase().split("").join(" ")})}A.exports=t;var e=/\s/,i=/(_|-|\.|:)/,T=/([a-z][A-Z]|[A-Z][a-z])/,E=/[\W_]+(.|$)/g,N=/(.)([A-Z]+)/g},function(A,M,t){function I(A){return g(A).replace(/[\W_]+(.|$)/g,function(A,M){return M?" "+M:""}).trim()}var g=t(507);A.exports=I},function(A,M){A.exports=function(){var A=document.getSelection();if(!A.rangeCount)return function(){};for(var M=document.activeElement,t=[],I=0;I=0||Object.prototype.hasOwnProperty.call(A,I)&&(t[I]=A[I]);return t}function i(A,M){function t(I,g){function i(A,t){var I=c.getLinkName(A),e=this.props[g[A]];I&&E(this.props,I)&&!e&&(e=this.props[I].requestChange);for(var i=arguments.length,T=Array(i>2?i-2:0),N=2;N=15||0===s[0]&&s[1]>=13?A:A.type}function T(A,M){var t=N(M);return t&&!E(A,M)&&E(A,t)?A[t].value:A[M]}function E(A,M){return void 0!==A[M]}function N(A){return"value"===A?"valueLink":"checked"===A?"checkedLink":null}function o(A){return"default"+A.charAt(0).toUpperCase()+A.substr(1)}function n(A,M,t){return function(){for(var I=arguments.length,g=Array(I),e=0;e1&&(I=t[0]+"@",A=t[1]),A=A.replace(z,".");var g=A.split("."),e=T(g,M).join(".");return I+e}function N(A){for(var M,t,I=[],g=0,e=A.length;g=55296&&M<=56319&&g65535&&(A-=65536,M+=m(A>>>10&1023|55296),A=56320|1023&A),M+=m(A)}).join("")}function n(A){return A-48<10?A-22:A-65<26?A-65:A-97<26?A-97:j}function C(A,M){return A+22+75*(A<26)-((0!=M)<<5)}function c(A,M,t){var I=0;for(A=t?O(A/L):A>>1,A+=O(A/M);A>U*u>>1;I+=j)A=O(A/U);return O(I+(U+1)*A/(A+w))}function D(A){var M,t,I,g,e,T,E,N,C,D,a=[],B=A.length,Q=0,r=Y,s=l;for(t=A.lastIndexOf(d),t<0&&(t=0),I=0;I=128&&i("not-basic"),a.push(A.charCodeAt(I));for(g=t>0?t+1:0;g=B&&i("invalid-input"),N=n(A.charCodeAt(g++)),(N>=j||N>O((x-Q)/T))&&i("overflow"),Q+=N*T,C=E<=s?y:E>=s+u?u:E-s,!(NO(x/D)&&i("overflow"),T*=D;M=a.length+1,s=c(Q-e,M,0==e),O(Q/M)>x-r&&i("overflow"),r+=O(Q/M),Q%=M,a.splice(Q++,0,r)}return o(a)}function a(A){var M,t,I,g,e,T,E,o,n,D,a,B,Q,r,s,w=[];for(A=N(A),B=A.length,M=Y,t=0,e=l,T=0;T=M&&aO((x-t)/Q)&&i("overflow"),t+=(E-M)*Q,M=E,T=0;Tx&&i("overflow"),a==M){for(o=t,n=j;D=n<=e?y:n>=e+u?u:n-e,!(o= 0x80 (not a basic code point)","invalid-input":"Invalid input"},U=j-y,O=Math.floor,m=String.fromCharCode;s={version:"1.3.2",ucs2:{decode:N,encode:o},decode:D,encode:a,toASCII:Q,toUnicode:B},I=function(){return s}.call(M,t,M,A),!(void 0!==I&&(A.exports=I))}(this)}).call(M,t(128)(A),function(){return this}())},function(A,M){"use strict";A.exports={isString:function(A){return"string"==typeof A},isObject:function(A){return"object"==typeof A&&null!==A},isNull:function(A){return null===A},isNullOrUndefined:function(A){return null==A}}}]);`) +},function(A,M,t){var I;(function(A,g){!function(e){function i(A){throw RangeError(p[A])}function T(A,M){for(var t=A.length,I=[];t--;)I[t]=M(A[t]);return I}function E(A,M){var t=A.split("@"),I="";t.length>1&&(I=t[0]+"@",A=t[1]),A=A.replace(z,".");var g=A.split("."),e=T(g,M).join(".");return I+e}function N(A){for(var M,t,I=[],g=0,e=A.length;g=55296&&M<=56319&&g65535&&(A-=65536,M+=m(A>>>10&1023|55296),A=56320|1023&A),M+=m(A)}).join("")}function n(A){return A-48<10?A-22:A-65<26?A-65:A-97<26?A-97:j}function C(A,M){return A+22+75*(A<26)-((0!=M)<<5)}function c(A,M,t){var I=0;for(A=t?O(A/l):A>>1,A+=O(A/M);A>U*u>>1;I+=j)A=O(A/U);return O(I+(U+1)*A/(A+w))}function D(A){var M,t,I,g,e,T,E,N,C,D,a=[],B=A.length,Q=0,r=Y,s=L;for(t=A.lastIndexOf(d),t<0&&(t=0),I=0;I=128&&i("not-basic"),a.push(A.charCodeAt(I));for(g=t>0?t+1:0;g=B&&i("invalid-input"),N=n(A.charCodeAt(g++)),(N>=j||N>O((x-Q)/T))&&i("overflow"),Q+=N*T,C=E<=s?y:E>=s+u?u:E-s,!(NO(x/D)&&i("overflow"),T*=D;M=a.length+1,s=c(Q-e,M,0==e),O(Q/M)>x-r&&i("overflow"),r+=O(Q/M),Q%=M,a.splice(Q++,0,r)}return o(a)}function a(A){var M,t,I,g,e,T,E,o,n,D,a,B,Q,r,s,w=[];for(A=N(A),B=A.length,M=Y,t=0,e=L,T=0;T=M&&aO((x-t)/Q)&&i("overflow"),t+=(E-M)*Q,M=E,T=0;Tx&&i("overflow"),a==M){for(o=t,n=j;D=n<=e?y:n>=e+u?u:n-e,!(o= 0x80 (not a basic code point)","invalid-input":"Invalid input"},U=j-y,O=Math.floor,m=String.fromCharCode;s={version:"1.3.2",ucs2:{decode:N,encode:o},decode:D,encode:a,toASCII:Q,toUnicode:B},I=function(){return s}.call(M,t,M,A),!(void 0!==I&&(A.exports=I))}(this)}).call(M,t(128)(A),function(){return this}())},function(A,M,t){"use strict";function I(){this.protocol=null,this.slashes=null,this.auth=null,this.host=null,this.port=null,this.hostname=null,this.hash=null,this.search=null,this.query=null,this.pathname=null,this.path=null,this.href=null}function g(A,M,t){if(A&&N.isObject(A)&&A instanceof I)return A;var g=new I;return g.parse(A,M,t),g}function e(A){return N.isString(A)&&(A=g(A)),A instanceof I?A.format():I.prototype.format.call(A)}function i(A,M){return g(A,!1,!0).resolve(M)}function T(A,M){return A?g(A,!1,!0).resolveObject(M):M}var E=t(524),N=t(526);M.parse=g,M.resolve=i,M.resolveObject=T,M.format=e,M.Url=I;var o=/^([a-z0-9.+-]+:)/i,n=/:[0-9]*$/,C=/^(\/\/?(?!\/)[^\?\s]*)(\?[^\s]*)?$/,c=["<",">",'"',"` + "`" + `"," ","\r","\n","\t"],D=["{","}","|","\\","^","` + "`" + `"].concat(c),a=["'"].concat(D),B=["%","/","?",";","#"].concat(a),Q=["/","?","#"],r=255,s=/^[+a-z0-9A-Z_-]{0,63}$/,x=/^([+a-z0-9A-Z_-]{0,63})(.*)$/,j={javascript:!0,"javascript:":!0},y={javascript:!0,"javascript:":!0},u={http:!0,https:!0,ftp:!0,gopher:!0,file:!0,"http:":!0,"https:":!0,"ftp:":!0,"gopher:":!0,"file:":!0},w=t(359);I.prototype.parse=function(A,M,t){if(!N.isString(A))throw new TypeError("Parameter 'url' must be a string, not "+typeof A);var I=A.indexOf("?"),g=I!==-1&&I127?"x":O[F];if(!m.match(s)){var k=p.slice(0,Y),R=p.slice(Y+1),J=O.match(x);J&&(k.push(J[1]),R.unshift(J[2])),R.length&&(T="/"+R.join(".")+T),this.hostname=k.join(".");break}}}this.hostname.length>r?this.hostname="":this.hostname=this.hostname.toLowerCase(),z||(this.hostname=E.toASCII(this.hostname));var G=this.port?":"+this.port:"",H=this.hostname||"";this.host=H+G,this.href+=this.host,z&&(this.hostname=this.hostname.substr(1,this.hostname.length-2),"/"!==T[0]&&(T="/"+T))}if(!j[D])for(var Y=0,U=a.length;Y0)&&t.host.split("@");l&&(t.auth=l.shift(),t.host=t.hostname=l.shift())}return t.search=A.search,t.query=A.query,N.isNull(t.pathname)&&N.isNull(t.search)||(t.path=(t.pathname?t.pathname:"")+(t.search?t.search:"")),t.href=t.format(),t}if(!j.length)return t.pathname=null,t.search?t.path="/"+t.search:t.path=null,t.href=t.format(),t;for(var L=j.slice(-1)[0],Y=(t.host||A.host||j.length>1)&&("."===L||".."===L)||""===L,d=0,h=j.length;h>=0;h--)L=j[h],"."===L?j.splice(h,1):".."===L?(j.splice(h,1),d++):d&&(j.splice(h,1),d--);if(!s&&!x)for(;d--;d)j.unshift("..");!s||""===j[0]||j[0]&&"/"===j[0].charAt(0)||j.unshift(""),Y&&"/"!==j.join("/").substr(-1)&&j.push("");var S=""===j[0]||j[0]&&"/"===j[0].charAt(0);if(w){t.hostname=t.host=S?"":j.length?j.shift():"";var l=!!(t.host&&t.host.indexOf("@")>0)&&t.host.split("@");l&&(t.auth=l.shift(),t.host=t.hostname=l.shift())}return s=s||t.host&&j.length,s&&!S&&j.unshift(""),j.length?t.pathname=j.join("/"):(t.pathname=null,t.path=null),N.isNull(t.pathname)&&N.isNull(t.search)||(t.path=(t.pathname?t.pathname:"")+(t.search?t.search:"")),t.auth=A.auth||t.auth,t.slashes=t.slashes||A.slashes,t.href=t.format(),t},I.prototype.parseHost=function(){var A=this.host,M=n.exec(A);M&&(M=M[0],":"!==M&&(this.port=M.substr(1)),A=A.substr(0,A.length-M.length)),A&&(this.hostname=A)}},function(A,M){"use strict";A.exports={isString:function(A){return"string"==typeof A},isObject:function(A){return"object"==typeof A&&null!==A},isNull:function(A){return null===A},isNullOrUndefined:function(A){return null==A}}}]);`) -func productionIndex_bundle20170215t224123zJsBytes() ([]byte, error) { - return _productionIndex_bundle20170215t224123zJs, nil +func productionIndex_bundle20170315t174951zJsBytes() ([]byte, error) { + return _productionIndex_bundle20170315t174951zJs, nil } -func productionIndex_bundle20170215t224123zJs() (*asset, error) { - bytes, err := productionIndex_bundle20170215t224123zJsBytes() +func productionIndex_bundle20170315t174951zJs() (*asset, error) { + bytes, err := productionIndex_bundle20170315t174951zJsBytes() if err != nil { return nil, err } - info := bindataFileInfo{name: "production/index_bundle-2017-02-15T22-41-23Z.js", size: 2276858, mode: os.FileMode(436), modTime: time.Unix(1487198497, 0)} + info := bindataFileInfo{name: "production/index_bundle-2017-03-15T17-49-51Z.js", size: 2283772, mode: os.FileMode(436), modTime: time.Unix(1489600214, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -370,7 +370,7 @@ func productionLoaderCss() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "production/loader.css", size: 1738, mode: os.FileMode(436), modTime: time.Unix(1487198497, 0)} + info := bindataFileInfo{name: "production/loader.css", size: 1738, mode: os.FileMode(436), modTime: time.Unix(1489600214, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -444,7 +444,7 @@ func productionLogoSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "production/logo.svg", size: 3079, mode: os.FileMode(436), modTime: time.Unix(1487198497, 0)} + info := bindataFileInfo{name: "production/logo.svg", size: 3079, mode: os.FileMode(436), modTime: time.Unix(1489600214, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -461,7 +461,7 @@ func productionSafariPng() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "production/safari.png", size: 4971, mode: os.FileMode(436), modTime: time.Unix(1487198497, 0)} + info := bindataFileInfo{name: "production/safari.png", size: 4971, mode: os.FileMode(436), modTime: time.Unix(1489600214, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -522,7 +522,7 @@ var _bindata = map[string]func() (*asset, error){ "production/favicon.ico": productionFaviconIco, "production/firefox.png": productionFirefoxPng, "production/index.html": productionIndexHTML, - "production/index_bundle-2017-02-15T22-41-23Z.js": productionIndex_bundle20170215t224123zJs, + "production/index_bundle-2017-03-15T17-49-51Z.js": productionIndex_bundle20170315t174951zJs, "production/loader.css": productionLoaderCss, "production/logo.svg": productionLogoSvg, "production/safari.png": productionSafariPng, @@ -574,7 +574,7 @@ var _bintree = &bintree{nil, map[string]*bintree{ "favicon.ico": {productionFaviconIco, map[string]*bintree{}}, "firefox.png": {productionFirefoxPng, map[string]*bintree{}}, "index.html": {productionIndexHTML, map[string]*bintree{}}, - "index_bundle-2017-02-15T22-41-23Z.js": {productionIndex_bundle20170215t224123zJs, map[string]*bintree{}}, + "index_bundle-2017-03-15T17-49-51Z.js": {productionIndex_bundle20170315t174951zJs, map[string]*bintree{}}, "loader.css": {productionLoaderCss, map[string]*bintree{}}, "logo.svg": {productionLogoSvg, map[string]*bintree{}}, "safari.png": {productionSafariPng, map[string]*bintree{}}, @@ -635,6 +635,6 @@ func assetFS() *assetfs.AssetFS { panic("unreachable") } -var UIReleaseTag = "RELEASE.2017-02-15T22-41-23Z" -var UICommitID = "b50e5164ba49acf3de57eb0dd5a45f1cd31b3411" -var UIVersion = "2017-02-15T22:41:23Z" +var UIReleaseTag = "RELEASE.2017-03-15T17-49-51Z" +var UICommitID = "ba0c11757ef3723de74fd13faa5c88bb79d2c889" +var UIVersion = "2017-03-15T17:49:51Z" diff --git a/browser/webpack.config.js b/browser/webpack.config.js index 3ccdaba0b..b8e86ff94 100644 --- a/browser/webpack.config.js +++ b/browser/webpack.config.js @@ -1,5 +1,5 @@ /* - * Minio Browser (C) 2016 Minio, Inc. + * Minio Cloud Storage (C) 2016 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,6 +22,7 @@ var purify = require("purifycss-webpack-plugin") var exports = { context: __dirname, entry: [ + "babel-polyfill", path.resolve(__dirname, 'app/index.js') ], output: { @@ -71,6 +72,10 @@ var exports = { target: 'http://localhost:9000', secure: false }, + '/minio/zip': { + target: 'http://localhost:9000', + secure: false + } } }, plugins: [ @@ -96,6 +101,7 @@ var exports = { if (process.env.NODE_ENV === 'dev') { exports.entry = [ + "babel-polyfill", 'webpack/hot/dev-server', 'webpack-dev-server/client?http://localhost:8080', path.resolve(__dirname, 'app/index.js') diff --git a/cmd/admin-handlers.go b/cmd/admin-handlers.go index 016db0a24..816263c3a 100644 --- a/cmd/admin-handlers.go +++ b/cmd/admin-handlers.go @@ -17,8 +17,10 @@ package cmd import ( + "bytes" "encoding/json" "encoding/xml" + "fmt" "io/ioutil" "net/http" "net/url" @@ -27,22 +29,26 @@ import ( ) const ( - minioAdminOpHeader = "X-Minio-Operation" + minioAdminOpHeader = "X-Minio-Operation" + minioConfigTmpFormat = "config-%s.json" ) // Type-safe query params. type mgmtQueryKey string -// Only valid query params for list/clear locks management APIs. +// Only valid query params for mgmt admin APIs. const ( - mgmtBucket mgmtQueryKey = "bucket" - mgmtObject mgmtQueryKey = "object" - mgmtPrefix mgmtQueryKey = "prefix" - mgmtLockDuration mgmtQueryKey = "duration" - mgmtDelimiter mgmtQueryKey = "delimiter" - mgmtMarker mgmtQueryKey = "marker" - mgmtMaxKey mgmtQueryKey = "max-key" - mgmtDryRun mgmtQueryKey = "dry-run" + mgmtBucket mgmtQueryKey = "bucket" + mgmtObject mgmtQueryKey = "object" + mgmtPrefix mgmtQueryKey = "prefix" + mgmtLockDuration mgmtQueryKey = "duration" + mgmtDelimiter mgmtQueryKey = "delimiter" + mgmtMarker mgmtQueryKey = "marker" + mgmtKeyMarker mgmtQueryKey = "key-marker" + mgmtMaxKey mgmtQueryKey = "max-key" + mgmtDryRun mgmtQueryKey = "dry-run" + mgmtUploadIDMarker mgmtQueryKey = "upload-id-marker" + mgmtMaxUploads mgmtQueryKey = "max-uploads" ) // ServerVersion - server version @@ -159,18 +165,12 @@ func (adminAPI adminAPIHandlers) ServiceCredentialsHandler(w http.ResponseWriter return } - // Check passed credentials - err = validateAuthKeys(req.Username, req.Password) + creds, err := createCredential(req.Username, req.Password) if err != nil { writeErrorResponse(w, toAPIErrorCode(err), r.URL) return } - creds := credential{ - AccessKey: req.Username, - SecretKey: req.Password, - } - // Notify all other Minio peers to update credentials updateErrs := updateCredsOnPeers(creds) for peer, err := range updateErrs { @@ -397,8 +397,57 @@ func (adminAPI adminAPIHandlers) ClearLocksHandler(w http.ResponseWriter, r *htt writeSuccessResponseJSON(w, jsonBytes) } -// validateHealQueryParams - Validates query params for heal list management API. -func validateHealQueryParams(vars url.Values) (string, string, string, string, int, APIErrorCode) { +// ListUploadsHealHandler - similar to listObjectsHealHandler +// GET +// /?heal&bucket=mybucket&prefix=myprefix&key-marker=mymarker&upload-id-marker=myuploadid&delimiter=mydelimiter&max-uploads=1000 +// - bucket is mandatory query parameter +// - rest are optional query parameters List upto maxKey objects that +// need healing in a given bucket matching the given prefix. +func (adminAPI adminAPIHandlers) ListUploadsHealHandler(w http.ResponseWriter, r *http.Request) { + // Get object layer instance. + objLayer := newObjectLayerFn() + if objLayer == nil { + writeErrorResponse(w, ErrServerNotInitialized, r.URL) + return + } + + // Validate request signature. + adminAPIErr := checkRequestAuthType(r, "", "", "") + if adminAPIErr != ErrNone { + writeErrorResponse(w, adminAPIErr, r.URL) + return + } + + // Validate query params. + vars := r.URL.Query() + bucket := vars.Get(string(mgmtBucket)) + prefix, keyMarker, uploadIDMarker, delimiter, maxUploads, _ := getBucketMultipartResources(r.URL.Query()) + + if err := checkListMultipartArgs(bucket, prefix, keyMarker, uploadIDMarker, delimiter, objLayer); err != nil { + writeErrorResponse(w, toAPIErrorCode(err), r.URL) + return + } + + if maxUploads <= 0 || maxUploads > maxUploadsList { + writeErrorResponse(w, ErrInvalidMaxUploads, r.URL) + return + } + + // Get the list objects to be healed. + listMultipartInfos, err := objLayer.ListUploadsHeal(bucket, prefix, + keyMarker, uploadIDMarker, delimiter, maxUploads) + if err != nil { + writeErrorResponse(w, toAPIErrorCode(err), r.URL) + return + } + + listResponse := generateListMultipartUploadsResponse(bucket, listMultipartInfos) + // Write success response. + writeSuccessResponseXML(w, encodeResponse(listResponse)) +} + +// extractListObjectsHealQuery - Validates query params for heal objects list management API. +func extractListObjectsHealQuery(vars url.Values) (string, string, string, string, int, APIErrorCode) { bucket := vars.Get(string(mgmtBucket)) prefix := vars.Get(string(mgmtPrefix)) marker := vars.Get(string(mgmtMarker)) @@ -415,10 +464,13 @@ func validateHealQueryParams(vars url.Values) (string, string, string, string, i return "", "", "", "", 0, ErrInvalidObjectName } - // check if maxKey is a valid integer. - maxKey, err := strconv.Atoi(maxKeyStr) - if err != nil { - return "", "", "", "", 0, ErrInvalidMaxKeys + // check if maxKey is a valid integer, if present. + var maxKey int + var err error + if maxKeyStr != "" { + if maxKey, err = strconv.Atoi(maxKeyStr); err != nil { + return "", "", "", "", 0, ErrInvalidMaxKeys + } } // Validate prefix, marker, delimiter and maxKey. @@ -451,7 +503,7 @@ func (adminAPI adminAPIHandlers) ListObjectsHealHandler(w http.ResponseWriter, r // Validate query params. vars := r.URL.Query() - bucket, prefix, marker, delimiter, maxKey, adminAPIErr := validateHealQueryParams(vars) + bucket, prefix, marker, delimiter, maxKey, adminAPIErr := extractListObjectsHealQuery(vars) if adminAPIErr != ErrNone { writeErrorResponse(w, adminAPIErr, r.URL) return @@ -672,3 +724,152 @@ func (adminAPI adminAPIHandlers) HealFormatHandler(w http.ResponseWriter, r *htt // Return 200 on success. writeSuccessResponseHeadersOnly(w) } + +// GetConfigHandler - GET /?config +// - x-minio-operation = get +// Get config.json of this minio setup. +func (adminAPI adminAPIHandlers) GetConfigHandler(w http.ResponseWriter, r *http.Request) { + // Validate request signature. + adminAPIErr := checkRequestAuthType(r, "", "", "") + if adminAPIErr != ErrNone { + writeErrorResponse(w, adminAPIErr, r.URL) + return + } + + // check if objectLayer is initialized, if not return. + if newObjectLayerFn() == nil { + writeErrorResponse(w, ErrServerNotInitialized, r.URL) + return + } + + // Get config.json from all nodes. In a single node setup, it + // returns local config.json. + configBytes, err := getPeerConfig(globalAdminPeers) + if err != nil { + errorIf(err, "Failed to get config from peers") + writeErrorResponse(w, toAdminAPIErrCode(err), r.URL) + return + } + + writeSuccessResponseJSON(w, configBytes) +} + +// toAdminAPIErrCode - converts errXLWriteQuorum error to admin API +// specific error. +func toAdminAPIErrCode(err error) APIErrorCode { + switch err { + case errXLWriteQuorum: + return ErrAdminConfigNoQuorum + } + return toAPIErrorCode(err) +} + +// SetConfigResult - represents detailed results of a set-config +// operation. +type nodeSummary struct { + Name string `json:"name"` + ErrSet bool `json:"errSet"` + ErrMsg string `json:"errMsg"` +} + +type setConfigResult struct { + NodeResults []nodeSummary `json:"nodeResults"` + Status bool `json:"status"` +} + +// writeSetConfigResponse - writes setConfigResult value as json depending on the status. +func writeSetConfigResponse(w http.ResponseWriter, peers adminPeers, errs []error, status bool, reqURL *url.URL) { + var nodeResults []nodeSummary + // Build nodeResults based on error values received during + // set-config operation. + for i := range errs { + nodeResults = append(nodeResults, nodeSummary{ + Name: peers[i].addr, + ErrSet: errs[i] != nil, + ErrMsg: fmt.Sprintf("%v", errs[i]), + }) + + } + + result := setConfigResult{ + Status: status, + NodeResults: nodeResults, + } + + // The following elaborate json encoding is to avoid escaping + // '<', '>' in . Note: json.Encoder.Encode() adds a + // gratuitous "\n". + var resultBuf bytes.Buffer + enc := json.NewEncoder(&resultBuf) + enc.SetEscapeHTML(false) + jsonErr := enc.Encode(result) + if jsonErr != nil { + writeErrorResponse(w, toAPIErrorCode(jsonErr), reqURL) + return + } + + writeSuccessResponseJSON(w, resultBuf.Bytes()) + return +} + +// SetConfigHandler - PUT /?config +// - x-minio-operation = set +func (adminAPI adminAPIHandlers) SetConfigHandler(w http.ResponseWriter, r *http.Request) { + // Get current object layer instance. + objectAPI := newObjectLayerFn() + if objectAPI == nil { + writeErrorResponse(w, ErrServerNotInitialized, r.URL) + return + } + + // Validate request signature. + adminAPIErr := checkRequestAuthType(r, "", "", "") + if adminAPIErr != ErrNone { + writeErrorResponse(w, adminAPIErr, r.URL) + return + } + + // Read configuration bytes from request body. + configBytes, err := ioutil.ReadAll(r.Body) + if err != nil { + errorIf(err, "Failed to read config from request body.") + writeErrorResponse(w, toAPIErrorCode(err), r.URL) + return + } + + // Write config received from request onto a temporary file on + // all nodes. + tmpFileName := fmt.Sprintf(minioConfigTmpFormat, mustGetUUID()) + errs := writeTmpConfigPeers(globalAdminPeers, tmpFileName, configBytes) + + // Check if the operation succeeded in quorum or more nodes. + rErr := reduceWriteQuorumErrs(errs, nil, len(globalAdminPeers)/2+1) + if rErr != nil { + writeSetConfigResponse(w, globalAdminPeers, errs, false, r.URL) + return + } + + // Take a lock on minio/config.json. NB minio is a reserved + // bucket name and wouldn't conflict with normal object + // operations. + configLock := globalNSMutex.NewNSLock(minioReservedBucket, minioConfigFile) + configLock.Lock() + defer configLock.Unlock() + + // Rename the temporary config file to config.json + errs = commitConfigPeers(globalAdminPeers, tmpFileName) + rErr = reduceWriteQuorumErrs(errs, nil, len(globalAdminPeers)/2+1) + if rErr != nil { + writeSetConfigResponse(w, globalAdminPeers, errs, false, r.URL) + return + } + + // serverMux (cmd/server-mux.go) implements graceful shutdown, + // where all listeners are closed and process restart/shutdown + // happens after 5s or completion of all ongoing http + // requests, whichever is earlier. + writeSetConfigResponse(w, globalAdminPeers, errs, true, r.URL) + + // Restart all node for the modified config to take effect. + sendServiceCmd(globalAdminPeers, serviceRestart) +} diff --git a/cmd/admin-handlers_test.go b/cmd/admin-handlers_test.go index e309e3f27..187c7c0a6 100644 --- a/cmd/admin-handlers_test.go +++ b/cmd/admin-handlers_test.go @@ -20,6 +20,7 @@ import ( "bytes" "encoding/json" "encoding/xml" + "fmt" "io/ioutil" "net/http" "net/http/httptest" @@ -30,6 +31,102 @@ import ( router "github.com/gorilla/mux" ) +var configJSON = []byte(`{ + "version": "13", + "credential": { + "accessKey": "minio", + "secretKey": "minio123" + }, + "region": "us-west-1", + "logger": { + "console": { + "enable": true, + "level": "fatal" + }, + "file": { + "enable": false, + "fileName": "", + "level": "" + } + }, + "notify": { + "amqp": { + "1": { + "enable": false, + "url": "", + "exchange": "", + "routingKey": "", + "exchangeType": "", + "mandatory": false, + "immediate": false, + "durable": false, + "internal": false, + "noWait": false, + "autoDeleted": false + } + }, + "nats": { + "1": { + "enable": false, + "address": "", + "subject": "", + "username": "", + "password": "", + "token": "", + "secure": false, + "pingInterval": 0, + "streaming": { + "enable": false, + "clusterID": "", + "clientID": "", + "async": false, + "maxPubAcksInflight": 0 + } + } + }, + "elasticsearch": { + "1": { + "enable": false, + "url": "", + "index": "" + } + }, + "redis": { + "1": { + "enable": false, + "address": "", + "password": "", + "key": "" + } + }, + "postgresql": { + "1": { + "enable": false, + "connectionString": "", + "table": "", + "host": "", + "port": "", + "user": "", + "password": "", + "database": "" + } + }, + "kafka": { + "1": { + "enable": false, + "brokers": null, + "topic": "" + } + }, + "webhook": { + "1": { + "enable": false, + "endpoint": "" + } + } + } +}`) + // adminXLTestBed - encapsulates subsystems that need to be setup for // admin-handler unit tests. type adminXLTestBed struct { @@ -643,7 +740,7 @@ func TestValidateHealQueryParams(t *testing.T) { } for i, test := range testCases { vars := mkListObjectsQueryVal(test.bucket, test.prefix, test.marker, test.delimiter, test.maxKeys) - _, _, _, _, _, actualErr := validateHealQueryParams(vars) + _, _, _, _, _, actualErr := extractListObjectsHealQuery(vars) if actualErr != test.apiErr { t.Errorf("Test %d - Expected %v but received %v", i+1, getAPIError(test.apiErr), getAPIError(actualErr)) @@ -759,9 +856,6 @@ func TestListObjectsHealHandler(t *testing.T) { } for i, test := range testCases { - if i != 0 { - continue - } queryVal := mkListObjectsQueryVal(test.bucket, test.prefix, test.marker, test.delimiter, test.maxKeys) req, err := newTestRequest("GET", "/?"+queryVal.Encode(), 0, nil) if err != nil { @@ -988,3 +1082,342 @@ func TestHealFormatHandler(t *testing.T) { t.Errorf("Expected to succeed but failed with %d", rec.Code) } } + +// TestGetConfigHandler - test for GetConfigHandler. +func TestGetConfigHandler(t *testing.T) { + adminTestBed, err := prepareAdminXLTestBed() + if err != nil { + t.Fatal("Failed to initialize a single node XL backend for admin handler tests.") + } + defer adminTestBed.TearDown() + + // Initialize admin peers to make admin RPC calls. + eps, err := parseStorageEndpoints([]string{"http://127.0.0.1"}) + if err != nil { + t.Fatalf("Failed to parse storage end point - %v", err) + } + + // Set globalMinioAddr to be able to distinguish local endpoints from remote. + globalMinioAddr = eps[0].Host + initGlobalAdminPeers(eps) + + // Prepare query params for get-config mgmt REST API. + queryVal := url.Values{} + queryVal.Set("config", "") + + req, err := newTestRequest("GET", "/?"+queryVal.Encode(), 0, nil) + if err != nil { + t.Fatalf("Failed to construct get-config object request - %v", err) + } + + // Set x-minio-operation header to get. + req.Header.Set(minioAdminOpHeader, "get") + + // Sign the request using signature v4. + cred := serverConfig.GetCredential() + err = signRequestV4(req, cred.AccessKey, cred.SecretKey) + if err != nil { + t.Fatalf("Failed to sign heal object request - %v", err) + } + + rec := httptest.NewRecorder() + adminTestBed.mux.ServeHTTP(rec, req) + if rec.Code != http.StatusOK { + t.Errorf("Expected to succeed but failed with %d", rec.Code) + } + +} + +// TestSetConfigHandler - test for SetConfigHandler. +func TestSetConfigHandler(t *testing.T) { + adminTestBed, err := prepareAdminXLTestBed() + if err != nil { + t.Fatal("Failed to initialize a single node XL backend for admin handler tests.") + } + defer adminTestBed.TearDown() + + // Initialize admin peers to make admin RPC calls. + eps, err := parseStorageEndpoints([]string{"http://127.0.0.1"}) + if err != nil { + t.Fatalf("Failed to parse storage end point - %v", err) + } + + // Set globalMinioAddr to be able to distinguish local endpoints from remote. + globalMinioAddr = eps[0].Host + initGlobalAdminPeers(eps) + + // SetConfigHandler restarts minio setup - need to start a + // signal receiver to receive on globalServiceSignalCh. + go testServiceSignalReceiver(restartCmd, t) + + // Prepare query params for set-config mgmt REST API. + queryVal := url.Values{} + queryVal.Set("config", "") + + req, err := newTestRequest("PUT", "/?"+queryVal.Encode(), int64(len(configJSON)), bytes.NewReader(configJSON)) + if err != nil { + t.Fatalf("Failed to construct get-config object request - %v", err) + } + + // Set x-minio-operation header to set. + req.Header.Set(minioAdminOpHeader, "set") + + // Sign the request using signature v4. + cred := serverConfig.GetCredential() + err = signRequestV4(req, cred.AccessKey, cred.SecretKey) + if err != nil { + t.Fatalf("Failed to sign heal object request - %v", err) + } + + rec := httptest.NewRecorder() + adminTestBed.mux.ServeHTTP(rec, req) + if rec.Code != http.StatusOK { + t.Errorf("Expected to succeed but failed with %d", rec.Code) + } + + result := setConfigResult{} + err = json.NewDecoder(rec.Body).Decode(&result) + if err != nil { + t.Fatalf("Failed to decode set config result json %v", err) + } + + if !result.Status { + t.Error("Expected set-config to succeed, but failed") + } +} + +// TestToAdminAPIErr - test for toAdminAPIErr helper function. +func TestToAdminAPIErr(t *testing.T) { + testCases := []struct { + err error + expectedAPIErr APIErrorCode + }{ + // 1. Server not in quorum. + { + err: errXLWriteQuorum, + expectedAPIErr: ErrAdminConfigNoQuorum, + }, + // 2. No error. + { + err: nil, + expectedAPIErr: ErrNone, + }, + // 3. Non-admin API specific error. + { + err: errDiskNotFound, + expectedAPIErr: toAPIErrorCode(errDiskNotFound), + }, + } + + for i, test := range testCases { + actualErr := toAdminAPIErrCode(test.err) + if actualErr != test.expectedAPIErr { + t.Errorf("Test %d: Expected %v but received %v", + i+1, test.expectedAPIErr, actualErr) + } + } +} + +func TestWriteSetConfigResponse(t *testing.T) { + testCases := []struct { + status bool + errs []error + }{ + // 1. all nodes returned success. + { + status: true, + errs: []error{nil, nil, nil, nil}, + }, + // 2. some nodes returned errors. + { + status: false, + errs: []error{errDiskNotFound, nil, errDiskAccessDenied, errFaultyDisk}, + }, + } + + testPeers := []adminPeer{ + { + addr: "localhost:9001", + }, + { + addr: "localhost:9002", + }, + { + addr: "localhost:9003", + }, + { + addr: "localhost:9004", + }, + } + + testURL, err := url.Parse("dummy.com") + if err != nil { + t.Fatalf("Failed to parse a place-holder url") + } + + var actualResult setConfigResult + for i, test := range testCases { + rec := httptest.NewRecorder() + writeSetConfigResponse(rec, testPeers, test.errs, test.status, testURL) + resp := rec.Result() + jsonBytes, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Fatalf("Test %d: Failed to read response %v", i+1, err) + } + + err = json.Unmarshal(jsonBytes, &actualResult) + if err != nil { + t.Fatalf("Test %d: Failed to unmarshal json %v", i+1, err) + } + if actualResult.Status != test.status { + t.Errorf("Test %d: Expected status %v but received %v", i+1, test.status, actualResult.Status) + } + for p, res := range actualResult.NodeResults { + if res.Name != testPeers[p].addr { + t.Errorf("Test %d: Expected node name %s but received %s", i+1, testPeers[p].addr, res.Name) + } + expectedErrMsg := fmt.Sprintf("%v", test.errs[p]) + if res.ErrMsg != expectedErrMsg { + t.Errorf("Test %d: Expected error %s but received %s", i+1, expectedErrMsg, res.ErrMsg) + } + expectedErrSet := test.errs[p] != nil + if res.ErrSet != expectedErrSet { + t.Errorf("Test %d: Expected ErrSet %v but received %v", i+1, expectedErrSet, res.ErrSet) + } + } + } +} + +// mkUploadsHealQuery - helper function to construct query values for +// listUploadsHeal. +func mkUploadsHealQuery(bucket, prefix, keyMarker, uploadIDMarker, delimiter, maxUploadsStr string) url.Values { + + queryVal := make(url.Values) + queryVal.Set("heal", "") + queryVal.Set(string(mgmtBucket), bucket) + queryVal.Set(string(mgmtPrefix), prefix) + queryVal.Set(string(mgmtKeyMarker), keyMarker) + queryVal.Set(string(mgmtUploadIDMarker), uploadIDMarker) + queryVal.Set(string(mgmtDelimiter), delimiter) + queryVal.Set(string(mgmtMaxUploads), maxUploadsStr) + return queryVal +} + +func TestListHealUploadsHandler(t *testing.T) { + adminTestBed, err := prepareAdminXLTestBed() + if err != nil { + t.Fatal("Failed to initialize a single node XL backend for admin handler tests.") + } + defer adminTestBed.TearDown() + + err = adminTestBed.objLayer.MakeBucket("mybucket") + if err != nil { + t.Fatalf("Failed to make bucket - %v", err) + } + + // Delete bucket after running all test cases. + defer adminTestBed.objLayer.DeleteBucket("mybucket") + + testCases := []struct { + bucket string + prefix string + keyMarker string + delimiter string + maxKeys string + statusCode int + }{ + // 1. Valid params. + { + bucket: "mybucket", + prefix: "prefix", + keyMarker: "prefix11", + delimiter: "/", + maxKeys: "10", + statusCode: http.StatusOK, + }, + // 2. Valid params with empty prefix. + { + bucket: "mybucket", + prefix: "", + keyMarker: "", + delimiter: "/", + maxKeys: "10", + statusCode: http.StatusOK, + }, + // 3. Invalid params with invalid bucket. + { + bucket: `invalid\\Bucket`, + prefix: "prefix", + keyMarker: "prefix11", + delimiter: "/", + maxKeys: "10", + statusCode: getAPIError(ErrInvalidBucketName).HTTPStatusCode, + }, + // 4. Invalid params with invalid prefix. + { + bucket: "mybucket", + prefix: `invalid\\Prefix`, + keyMarker: "prefix11", + delimiter: "/", + maxKeys: "10", + statusCode: getAPIError(ErrInvalidObjectName).HTTPStatusCode, + }, + // 5. Invalid params with invalid maxKeys. + { + bucket: "mybucket", + prefix: "prefix", + keyMarker: "prefix11", + delimiter: "/", + maxKeys: "-1", + statusCode: getAPIError(ErrInvalidMaxUploads).HTTPStatusCode, + }, + // 6. Invalid params with unsupported prefix marker combination. + { + bucket: "mybucket", + prefix: "prefix", + keyMarker: "notmatchingmarker", + delimiter: "/", + maxKeys: "10", + statusCode: getAPIError(ErrNotImplemented).HTTPStatusCode, + }, + // 7. Invalid params with unsupported delimiter. + { + bucket: "mybucket", + prefix: "prefix", + keyMarker: "notmatchingmarker", + delimiter: "unsupported", + maxKeys: "10", + statusCode: getAPIError(ErrNotImplemented).HTTPStatusCode, + }, + // 8. Invalid params with invalid max Keys + { + bucket: "mybucket", + prefix: "prefix", + keyMarker: "prefix11", + delimiter: "/", + maxKeys: "999999999999999999999999999", + statusCode: getAPIError(ErrInvalidMaxUploads).HTTPStatusCode, + }, + } + + for i, test := range testCases { + queryVal := mkUploadsHealQuery(test.bucket, test.prefix, test.keyMarker, "", test.delimiter, test.maxKeys) + + req, err := newTestRequest("GET", "/?"+queryVal.Encode(), 0, nil) + if err != nil { + t.Fatalf("Test %d - Failed to construct list uploads needing heal request - %v", i+1, err) + } + req.Header.Set(minioAdminOpHeader, "list-uploads") + + cred := serverConfig.GetCredential() + err = signRequestV4(req, cred.AccessKey, cred.SecretKey) + if err != nil { + t.Fatalf("Test %d - Failed to sign list uploads needing heal request - %v", i+1, err) + } + rec := httptest.NewRecorder() + adminTestBed.mux.ServeHTTP(rec, req) + if test.statusCode != rec.Code { + t.Errorf("Test %d - Expected HTTP status code %d but received %d", i+1, test.statusCode, rec.Code) + } + } +} diff --git a/cmd/admin-router.go b/cmd/admin-router.go index b22794c97..b65c4eda2 100644 --- a/cmd/admin-router.go +++ b/cmd/admin-router.go @@ -53,6 +53,8 @@ func registerAdminRouter(mux *router.Router) { // List Objects needing heal. adminRouter.Methods("GET").Queries("heal", "").Headers(minioAdminOpHeader, "list-objects").HandlerFunc(adminAPI.ListObjectsHealHandler) + // List Uploads needing heal. + adminRouter.Methods("GET").Queries("heal", "").Headers(minioAdminOpHeader, "list-uploads").HandlerFunc(adminAPI.ListUploadsHealHandler) // List Buckets needing heal. adminRouter.Methods("GET").Queries("heal", "").Headers(minioAdminOpHeader, "list-buckets").HandlerFunc(adminAPI.ListBucketsHealHandler) @@ -62,4 +64,11 @@ func registerAdminRouter(mux *router.Router) { adminRouter.Methods("POST").Queries("heal", "").Headers(minioAdminOpHeader, "object").HandlerFunc(adminAPI.HealObjectHandler) // Heal Format. adminRouter.Methods("POST").Queries("heal", "").Headers(minioAdminOpHeader, "format").HandlerFunc(adminAPI.HealFormatHandler) + + /// Config operations + + // Get config + adminRouter.Methods("GET").Queries("config", "").Headers(minioAdminOpHeader, "get").HandlerFunc(adminAPI.GetConfigHandler) + // Set Config + adminRouter.Methods("PUT").Queries("config", "").Headers(minioAdminOpHeader, "set").HandlerFunc(adminAPI.SetConfigHandler) } diff --git a/cmd/admin-rpc-client.go b/cmd/admin-rpc-client.go index 6cb0709be..a1a482878 100644 --- a/cmd/admin-rpc-client.go +++ b/cmd/admin-rpc-client.go @@ -17,13 +17,30 @@ package cmd import ( + "encoding/json" + "errors" + "fmt" "net/url" + "os" "path" + "path/filepath" + "reflect" "sort" "sync" "time" ) +const ( + // Admin service names + serviceRestartRPC = "Admin.Restart" + listLocksRPC = "Admin.ListLocks" + reInitDisksRPC = "Admin.ReInitDisks" + uptimeRPC = "Admin.Uptime" + getConfigRPC = "Admin.GetConfig" + writeTmpConfigRPC = "Admin.WriteTmpConfig" + commitConfigRPC = "Admin.CommitConfig" +) + // localAdminClient - represents admin operation to be executed locally. type localAdminClient struct { } @@ -41,6 +58,9 @@ type adminCmdRunner interface { ListLocks(bucket, prefix string, duration time.Duration) ([]VolumeLockInfo, error) ReInitDisks() error Uptime() (time.Duration, error) + GetConfig() ([]byte, error) + WriteTmpConfig(tmpFileName string, configBytes []byte) error + CommitConfig(tmpFileName string) error } // Restart - Sends a message over channel to the go-routine @@ -59,7 +79,7 @@ func (lc localAdminClient) ListLocks(bucket, prefix string, duration time.Durati func (rc remoteAdminClient) Restart() error { args := AuthRPCArgs{} reply := AuthRPCReply{} - return rc.Call("Admin.Restart", &args, &reply) + return rc.Call(serviceRestartRPC, &args, &reply) } // ListLocks - Sends list locks command to remote server via RPC. @@ -70,7 +90,7 @@ func (rc remoteAdminClient) ListLocks(bucket, prefix string, duration time.Durat duration: duration, } var reply ListLocksReply - if err := rc.Call("Admin.ListLocks", &listArgs, &reply); err != nil { + if err := rc.Call(listLocksRPC, &listArgs, &reply); err != nil { return nil, err } return reply.volLocks, nil @@ -87,7 +107,7 @@ func (lc localAdminClient) ReInitDisks() error { func (rc remoteAdminClient) ReInitDisks() error { args := AuthRPCArgs{} reply := AuthRPCReply{} - return rc.Call("Admin.ReInitDisks", &args, &reply) + return rc.Call(reInitDisksRPC, &args, &reply) } // Uptime - Returns the uptime of this server. Timestamp is taken @@ -104,7 +124,7 @@ func (lc localAdminClient) Uptime() (time.Duration, error) { func (rc remoteAdminClient) Uptime() (time.Duration, error) { args := AuthRPCArgs{} reply := UptimeReply{} - err := rc.Call("Admin.Uptime", &args, &reply) + err := rc.Call(uptimeRPC, &args, &reply) if err != nil { return time.Duration(0), err } @@ -112,6 +132,75 @@ func (rc remoteAdminClient) Uptime() (time.Duration, error) { return reply.Uptime, nil } +// GetConfig - returns config.json of the local server. +func (lc localAdminClient) GetConfig() ([]byte, error) { + if serverConfig == nil { + return nil, errors.New("config not present") + } + + return json.Marshal(serverConfig) +} + +// GetConfig - returns config.json of the remote server. +func (rc remoteAdminClient) GetConfig() ([]byte, error) { + args := AuthRPCArgs{} + reply := ConfigReply{} + if err := rc.Call(getConfigRPC, &args, &reply); err != nil { + return nil, err + } + return reply.Config, nil +} + +// WriteTmpConfig - writes config file content to a temporary file on +// the local server. +func (lc localAdminClient) WriteTmpConfig(tmpFileName string, configBytes []byte) error { + return writeTmpConfigCommon(tmpFileName, configBytes) +} + +// WriteTmpConfig - writes config file content to a temporary file on +// a remote node. +func (rc remoteAdminClient) WriteTmpConfig(tmpFileName string, configBytes []byte) error { + wArgs := WriteConfigArgs{ + TmpFileName: tmpFileName, + Buf: configBytes, + } + + err := rc.Call(writeTmpConfigRPC, &wArgs, &WriteConfigReply{}) + if err != nil { + errorIf(err, "Failed to write temporary config file.") + return err + } + + return nil +} + +// CommitConfig - Move the new config in tmpFileName onto config.json +// on a local node. +func (lc localAdminClient) CommitConfig(tmpFileName string) error { + configFile := getConfigFile() + tmpConfigFile := filepath.Join(getConfigDir(), tmpFileName) + + err := os.Rename(tmpConfigFile, configFile) + errorIf(err, fmt.Sprintf("Failed to rename %s to %s", tmpConfigFile, configFile)) + return err +} + +// CommitConfig - Move the new config in tmpFileName onto config.json +// on a remote node. +func (rc remoteAdminClient) CommitConfig(tmpFileName string) error { + cArgs := CommitConfigArgs{ + FileName: tmpFileName, + } + cReply := CommitConfigReply{} + err := rc.Call(commitConfigRPC, &cArgs, &cReply) + if err != nil { + errorIf(err, "Failed to rename config file.") + return err + } + + return nil +} + // adminPeer - represents an entity that implements Restart methods. type adminPeer struct { addr string @@ -150,7 +239,7 @@ func makeAdminPeers(eps []*url.URL) adminPeers { secretKey: serverCred.SecretKey, serverAddr: ep.Host, secureConn: globalIsSSL, - serviceEndpoint: path.Join(reservedBucket, adminPath), + serviceEndpoint: path.Join(minioReservedBucketPath, adminPath), serviceName: "Admin", } @@ -336,3 +425,185 @@ func getPeerUptimes(peers adminPeers) (time.Duration, error) { return latestUptime, nil } + +// getPeerConfig - Fetches config.json from all nodes in the setup and +// returns the one that occurs in a majority of them. +func getPeerConfig(peers adminPeers) ([]byte, error) { + if !globalIsDistXL { + return peers[0].cmdRunner.GetConfig() + } + + errs := make([]error, len(peers)) + configs := make([][]byte, len(peers)) + + // Get config from all servers. + wg := sync.WaitGroup{} + for i, peer := range peers { + wg.Add(1) + go func(idx int, peer adminPeer) { + defer wg.Done() + configs[idx], errs[idx] = peer.cmdRunner.GetConfig() + }(i, peer) + } + wg.Wait() + + // Find the maximally occurring config among peers in a + // distributed setup. + + serverConfigs := make([]serverConfigV13, len(peers)) + for i, configBytes := range configs { + if errs[i] != nil { + continue + } + + // Unmarshal the received config files. + err := json.Unmarshal(configBytes, &serverConfigs[i]) + if err != nil { + errorIf(err, "Failed to unmarshal serverConfig from ", peers[i].addr) + return nil, err + } + } + + configJSON, err := getValidServerConfig(serverConfigs, errs) + if err != nil { + errorIf(err, "Unable to find a valid server config") + return nil, traceError(err) + } + + // Return the config.json that was present quorum or more + // number of disks. + return json.Marshal(configJSON) +} + +// getValidServerConfig - finds the server config that is present in +// quorum or more number of servers. +func getValidServerConfig(serverConfigs []serverConfigV13, errs []error) (serverConfigV13, error) { + // majority-based quorum + quorum := len(serverConfigs)/2 + 1 + + // Count the number of disks a config.json was found in. + configCounter := make([]int, len(serverConfigs)) + + // We group equal serverConfigs by the lowest index of the + // same value; e.g, let us take the following serverConfigs + // in a 4-node setup, + // serverConfigs == [c1, c2, c1, c1] + // configCounter == [3, 1, 0, 0] + // c1, c2 are the only distinct values that appear. c1 is + // identified by 0, the lowest index it appears in and c2 is + // identified by 1. So, we need to find the number of times + // each of these distinct values occur. + + // Invariants: + + // 1. At the beginning of the i-th iteration, the number of + // unique configurations seen so far is equal to the number of + // non-zero counter values in config[:i]. + + // 2. At the beginning of the i-th iteration, the sum of + // elements of configCounter[:i] is equal to the number of + // non-error configurations seen so far. + + // For each of the serverConfig ... + for i := range serverConfigs { + // Skip nodes where getConfig failed. + if errs[i] != nil { + continue + } + // Check if it is equal to any of the configurations + // seen so far. If j == i is reached then we have an + // unseen configuration. + for j := 0; j <= i; j++ { + if j < i && configCounter[j] == 0 { + // serverConfigs[j] is known to be + // equal to a value that was already + // seen. See example above for + // clarity. + continue + } else if j < i && reflect.DeepEqual(serverConfigs[i], serverConfigs[j]) { + // serverConfigs[i] is equal to + // serverConfigs[j], update + // serverConfigs[j]'s counter since it + // is the lower index. + configCounter[j]++ + break + } else if j == i { + // serverConfigs[i] is equal to no + // other value seen before. It is + // unique so far. + configCounter[i] = 1 + break + } // else invariants specified above are violated. + } + } + + // We find the maximally occurring server config and check if + // there is quorum. + var configJSON serverConfigV13 + maxOccurrence := 0 + for i, count := range configCounter { + if maxOccurrence < count { + maxOccurrence = count + configJSON = serverConfigs[i] + } + } + + // If quorum nodes don't agree. + if maxOccurrence < quorum { + return serverConfigV13{}, errXLWriteQuorum + } + + return configJSON, nil +} + +// Write config contents into a temporary file on all nodes. +func writeTmpConfigPeers(peers adminPeers, tmpFileName string, configBytes []byte) []error { + // For a single-node minio server setup. + if !globalIsDistXL { + err := peers[0].cmdRunner.WriteTmpConfig(tmpFileName, configBytes) + return []error{err} + } + + errs := make([]error, len(peers)) + + // Write config into temporary file on all nodes. + wg := sync.WaitGroup{} + for i, peer := range peers { + wg.Add(1) + go func(idx int, peer adminPeer) { + defer wg.Done() + errs[idx] = peer.cmdRunner.WriteTmpConfig(tmpFileName, configBytes) + }(i, peer) + } + wg.Wait() + + // Return bytes written and errors (if any) during writing + // temporary config file. + return errs +} + +// Move config contents from the given temporary file onto config.json +// on all nodes. +func commitConfigPeers(peers adminPeers, tmpFileName string) []error { + // For a single-node minio server setup. + if !globalIsDistXL { + return []error{peers[0].cmdRunner.CommitConfig(tmpFileName)} + } + + errs := make([]error, len(peers)) + + // Rename temporary config file into configDir/config.json on + // all nodes. + wg := sync.WaitGroup{} + for i, peer := range peers { + wg.Add(1) + go func(idx int, peer adminPeer) { + defer wg.Done() + errs[idx] = peer.cmdRunner.CommitConfig(tmpFileName) + }(i, peer) + } + wg.Wait() + + // Return errors (if any) received during rename. + return errs +} diff --git a/cmd/admin-rpc-client_test.go b/cmd/admin-rpc-client_test.go new file mode 100644 index 000000000..6bd360a72 --- /dev/null +++ b/cmd/admin-rpc-client_test.go @@ -0,0 +1,260 @@ +/* + * Minio Cloud Storage, (C) 2017 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cmd + +import ( + "encoding/json" + "reflect" + "testing" +) + +var ( + config1 = []byte(`{ + "version": "13", + "credential": { + "accessKey": "minio", + "secretKey": "minio123" + }, + "region": "us-east-1", + "logger": { + "console": { + "enable": true, + "level": "debug" + }, + "file": { + "enable": false, + "fileName": "", + "level": "" + } + }, + "notify": { + "amqp": { + "1": { + "enable": false, + "url": "", + "exchange": "", + "routingKey": "", + "exchangeType": "", + "mandatory": false, + "immediate": false, + "durable": false, + "internal": false, + "noWait": false, + "autoDeleted": false + } + }, + "nats": { + "1": { + "enable": false, + "address": "", + "subject": "", + "username": "", + "password": "", + "token": "", + "secure": false, + "pingInterval": 0, + "streaming": { + "enable": false, + "clusterID": "", + "clientID": "", + "async": false, + "maxPubAcksInflight": 0 + } + } + }, + "elasticsearch": { + "1": { + "enable": false, + "url": "", + "index": "" + } + }, + "redis": { + "1": { + "enable": false, + "address": "", + "password": "", + "key": "" + } + }, + "postgresql": { + "1": { + "enable": false, + "connectionString": "", + "table": "", + "host": "", + "port": "", + "user": "", + "password": "", + "database": "" + } + }, + "kafka": { + "1": { + "enable": false, + "brokers": null, + "topic": "" + } + }, + "webhook": { + "1": { + "enable": false, + "endpoint": "" + } + } + } +} +`) + // diff from config1 - amqp.Enable is True + config2 = []byte(`{ + "version": "13", + "credential": { + "accessKey": "minio", + "secretKey": "minio123" + }, + "region": "us-east-1", + "logger": { + "console": { + "enable": true, + "level": "debug" + }, + "file": { + "enable": false, + "fileName": "", + "level": "" + } + }, + "notify": { + "amqp": { + "1": { + "enable": true, + "url": "", + "exchange": "", + "routingKey": "", + "exchangeType": "", + "mandatory": false, + "immediate": false, + "durable": false, + "internal": false, + "noWait": false, + "autoDeleted": false + } + }, + "nats": { + "1": { + "enable": false, + "address": "", + "subject": "", + "username": "", + "password": "", + "token": "", + "secure": false, + "pingInterval": 0, + "streaming": { + "enable": false, + "clusterID": "", + "clientID": "", + "async": false, + "maxPubAcksInflight": 0 + } + } + }, + "elasticsearch": { + "1": { + "enable": false, + "url": "", + "index": "" + } + }, + "redis": { + "1": { + "enable": false, + "address": "", + "password": "", + "key": "" + } + }, + "postgresql": { + "1": { + "enable": false, + "connectionString": "", + "table": "", + "host": "", + "port": "", + "user": "", + "password": "", + "database": "" + } + }, + "kafka": { + "1": { + "enable": false, + "brokers": null, + "topic": "" + } + }, + "webhook": { + "1": { + "enable": false, + "endpoint": "" + } + } + } +} +`) +) + +// TestGetValidServerConfig - test for getValidServerConfig. +func TestGetValidServerConfig(t *testing.T) { + var c1, c2 serverConfigV13 + err := json.Unmarshal(config1, &c1) + if err != nil { + t.Fatalf("json unmarshal of %s failed: %v", string(config1), err) + } + + err = json.Unmarshal(config2, &c2) + if err != nil { + t.Fatalf("json unmarshal of %s failed: %v", string(config2), err) + } + + // Valid config. + noErrs := []error{nil, nil, nil, nil} + serverConfigs := []serverConfigV13{c1, c2, c1, c1} + validConfig, err := getValidServerConfig(serverConfigs, noErrs) + if err != nil { + t.Errorf("Expected a valid config but received %v instead", err) + } + + if !reflect.DeepEqual(validConfig, c1) { + t.Errorf("Expected valid config to be %v but received %v", config1, validConfig) + } + + // Invalid config - no quorum. + serverConfigs = []serverConfigV13{c1, c2, c2, c1} + validConfig, err = getValidServerConfig(serverConfigs, noErrs) + if err != errXLWriteQuorum { + t.Errorf("Expected to fail due to lack of quorum but received %v", err) + } + + // All errors + allErrs := []error{errDiskNotFound, errDiskNotFound, errDiskNotFound, errDiskNotFound} + serverConfigs = []serverConfigV13{{}, {}, {}, {}} + validConfig, err = getValidServerConfig(serverConfigs, allErrs) + if err != errXLWriteQuorum { + t.Errorf("Expected to fail due to lack of quorum but received %v", err) + } +} diff --git a/cmd/admin-rpc-server.go b/cmd/admin-rpc-server.go index d5f8178bd..855044a64 100644 --- a/cmd/admin-rpc-server.go +++ b/cmd/admin-rpc-server.go @@ -17,8 +17,13 @@ package cmd import ( + "encoding/json" "errors" + "fmt" + "io/ioutil" "net/rpc" + "os" + "path/filepath" "time" router "github.com/gorilla/mux" @@ -54,6 +59,12 @@ type UptimeReply struct { Uptime time.Duration } +// ConfigReply - wraps the server config response over RPC. +type ConfigReply struct { + AuthRPCReply + Config []byte // json-marshalled bytes of serverConfigV13 +} + // Restart - Restart this instance of minio server. func (s *adminCmd) Restart(args *AuthRPCArgs, reply *AuthRPCReply) error { if err := args.IsAuthenticated(); err != nil { @@ -132,6 +143,78 @@ func (s *adminCmd) Uptime(args *AuthRPCArgs, reply *UptimeReply) error { return nil } +// GetConfig - returns the config.json of this server. +func (s *adminCmd) GetConfig(args *AuthRPCArgs, reply *ConfigReply) error { + if err := args.IsAuthenticated(); err != nil { + return err + } + + if serverConfig == nil { + return errors.New("config not present") + } + + jsonBytes, err := json.Marshal(serverConfig) + if err != nil { + return err + } + + reply.Config = jsonBytes + return nil +} + +// WriteConfigArgs - wraps the bytes to be written and temporary file name. +type WriteConfigArgs struct { + AuthRPCArgs + TmpFileName string + Buf []byte +} + +// WriteConfigReply - wraps the result of a writing config into a temporary file. +// the remote node. +type WriteConfigReply struct { + AuthRPCReply +} + +func writeTmpConfigCommon(tmpFileName string, configBytes []byte) error { + tmpConfigFile := filepath.Join(getConfigDir(), tmpFileName) + err := ioutil.WriteFile(tmpConfigFile, configBytes, 0666) + errorIf(err, fmt.Sprintf("Failed to write to temporary config file %s", tmpConfigFile)) + return err +} + +// WriteTmpConfig - writes the supplied config contents onto the +// supplied temporary file. +func (s *adminCmd) WriteTmpConfig(wArgs *WriteConfigArgs, wReply *WriteConfigReply) error { + if err := wArgs.IsAuthenticated(); err != nil { + return err + } + + return writeTmpConfigCommon(wArgs.TmpFileName, wArgs.Buf) +} + +// CommitConfigArgs - wraps the config file name that needs to be +// committed into config.json on this node. +type CommitConfigArgs struct { + AuthRPCArgs + FileName string +} + +// CommitConfigReply - represents response to commit of config file on +// this node. +type CommitConfigReply struct { + AuthRPCReply +} + +// CommitConfig - Renames the temporary file into config.json on this node. +func (s *adminCmd) CommitConfig(cArgs *CommitConfigArgs, cReply *CommitConfigReply) error { + configFile := getConfigFile() + tmpConfigFile := filepath.Join(getConfigDir(), cArgs.FileName) + + err := os.Rename(tmpConfigFile, configFile) + errorIf(err, fmt.Sprintf("Failed to rename %s to %s", tmpConfigFile, configFile)) + return err +} + // registerAdminRPCRouter - registers RPC methods for service status, // stop and restart commands. func registerAdminRPCRouter(mux *router.Router) error { @@ -141,7 +224,7 @@ func registerAdminRPCRouter(mux *router.Router) error { if err != nil { return traceError(err) } - adminRouter := mux.NewRoute().PathPrefix(reservedBucket).Subrouter() + adminRouter := mux.NewRoute().PathPrefix(minioReservedBucketPath).Subrouter() adminRouter.Path(adminPath).Handler(adminRPCServer) return nil } diff --git a/cmd/admin-rpc-server_test.go b/cmd/admin-rpc-server_test.go index 32d1dd288..f41c4e28d 100644 --- a/cmd/admin-rpc-server_test.go +++ b/cmd/admin-rpc-server_test.go @@ -17,6 +17,7 @@ package cmd import ( + "encoding/json" "net/url" "testing" "time" @@ -52,7 +53,7 @@ func testAdminCmd(cmd cmdType, t *testing.T) { <-globalServiceSignalCh }() - ga := AuthRPCArgs{AuthToken: reply.AuthToken, RequestTime: time.Now().UTC()} + ga := AuthRPCArgs{AuthToken: reply.AuthToken} genReply := AuthRPCReply{} switch cmd { case restartCmd: @@ -107,8 +108,7 @@ func TestReInitDisks(t *testing.T) { } authArgs := AuthRPCArgs{ - AuthToken: reply.AuthToken, - RequestTime: time.Now().UTC(), + AuthToken: reply.AuthToken, } authReply := AuthRPCReply{} @@ -133,8 +133,7 @@ func TestReInitDisks(t *testing.T) { } authArgs = AuthRPCArgs{ - AuthToken: fsReply.AuthToken, - RequestTime: time.Now().UTC(), + AuthToken: fsReply.AuthToken, } authReply = AuthRPCReply{} // Attempt ReInitDisks service on a FS backend. @@ -144,3 +143,106 @@ func TestReInitDisks(t *testing.T) { errUnsupportedBackend, err) } } + +// TestGetConfig - Test for GetConfig admin RPC. +func TestGetConfig(t *testing.T) { + // Reset global variables to start afresh. + resetTestGlobals() + + rootPath, err := newTestConfig("us-east-1") + if err != nil { + t.Fatalf("Unable to initialize server config. %s", err) + } + defer removeAll(rootPath) + + adminServer := adminCmd{} + creds := serverConfig.GetCredential() + args := LoginRPCArgs{ + Username: creds.AccessKey, + Password: creds.SecretKey, + Version: Version, + RequestTime: time.Now().UTC(), + } + reply := LoginRPCReply{} + err = adminServer.Login(&args, &reply) + if err != nil { + t.Fatalf("Failed to login to admin server - %v", err) + } + + authArgs := AuthRPCArgs{ + AuthToken: reply.AuthToken, + } + + configReply := ConfigReply{} + + err = adminServer.GetConfig(&authArgs, &configReply) + if err != nil { + t.Errorf("Expected GetConfig to pass but failed with %v", err) + } + + var config serverConfigV13 + err = json.Unmarshal(configReply.Config, &config) + if err != nil { + t.Errorf("Expected json unmarshal to pass but failed with %v", err) + } +} + +// TestWriteAndCommitConfig - test for WriteTmpConfig and CommitConfig +// RPC handler. +func TestWriteAndCommitConfig(t *testing.T) { + // Reset global variables to start afresh. + resetTestGlobals() + + rootPath, err := newTestConfig("us-east-1") + if err != nil { + t.Fatalf("Unable to initialize server config. %s", err) + } + defer removeAll(rootPath) + + adminServer := adminCmd{} + creds := serverConfig.GetCredential() + args := LoginRPCArgs{ + Username: creds.AccessKey, + Password: creds.SecretKey, + Version: Version, + RequestTime: time.Now().UTC(), + } + reply := LoginRPCReply{} + err = adminServer.Login(&args, &reply) + if err != nil { + t.Fatalf("Failed to login to admin server - %v", err) + } + + // Write temporary config. + buf := []byte("hello") + tmpFileName := mustGetUUID() + wArgs := WriteConfigArgs{ + AuthRPCArgs: AuthRPCArgs{ + AuthToken: reply.AuthToken, + }, + TmpFileName: tmpFileName, + Buf: buf, + } + + err = adminServer.WriteTmpConfig(&wArgs, &WriteConfigReply{}) + if err != nil { + t.Fatalf("Failed to write temporary config %v", err) + } + + if err != nil { + t.Errorf("Expected to succeed but failed %v", err) + } + + cArgs := CommitConfigArgs{ + AuthRPCArgs: AuthRPCArgs{ + AuthToken: reply.AuthToken, + }, + FileName: tmpFileName, + } + cReply := CommitConfigReply{} + + err = adminServer.CommitConfig(&cArgs, &cReply) + if err != nil { + t.Fatalf("Failed to commit config file %v", err) + } +} diff --git a/cmd/api-errors.go b/cmd/api-errors.go index 8eb82761a..7d061495e 100644 --- a/cmd/api-errors.go +++ b/cmd/api-errors.go @@ -56,6 +56,8 @@ const ( ErrInvalidBucketName ErrInvalidDigest ErrInvalidRange + ErrInvalidCopyPartRange + ErrInvalidCopyPartRangeSource ErrInvalidMaxKeys ErrInvalidMaxUploads ErrInvalidMaxParts @@ -144,6 +146,7 @@ const ( ErrAdminInvalidAccessKey ErrAdminInvalidSecretKey + ErrAdminConfigNoQuorum ) // error code to APIError structure, these fields carry respective @@ -539,6 +542,16 @@ var errorCodeResponse = map[APIErrorCode]APIError{ Description: "Configurations overlap. Configurations on the same bucket cannot share a common event type.", HTTPStatusCode: http.StatusBadRequest, }, + ErrInvalidCopyPartRange: { + Code: "InvalidArgument", + Description: "The x-amz-copy-source-range value must be of the form bytes=first-last where first and last are the zero-based offsets of the first and last bytes to copy", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrInvalidCopyPartRangeSource: { + Code: "InvalidArgument", + Description: "Range specified is not valid for source object", + HTTPStatusCode: http.StatusBadRequest, + }, /// S3 extensions. ErrContentSHA256Mismatch: { @@ -593,6 +606,11 @@ var errorCodeResponse = map[APIErrorCode]APIError{ Description: "The secret key is invalid.", HTTPStatusCode: http.StatusBadRequest, }, + ErrAdminConfigNoQuorum: { + Code: "XMinioAdminConfigNoQuorum", + Description: "Configuration update failed because server quorum was not met", + HTTPStatusCode: http.StatusServiceUnavailable, + }, // Add your error structure here. } @@ -674,6 +692,8 @@ func toAPIErrorCode(err error) (apiErr APIErrorCode) { apiErr = ErrEntityTooLarge case ObjectTooSmall: apiErr = ErrEntityTooSmall + case NotImplemented: + apiErr = ErrNotImplemented default: apiErr = ErrInternalError } diff --git a/cmd/api-errors_test.go b/cmd/api-errors_test.go index ca5f653ec..7a4b6fde0 100644 --- a/cmd/api-errors_test.go +++ b/cmd/api-errors_test.go @@ -103,6 +103,10 @@ func TestAPIErrCode(t *testing.T) { StorageFull{}, ErrStorageFull, }, + { + NotImplemented{}, + ErrNotImplemented, + }, { errSignatureMismatch, ErrSignatureDoesNotMatch, diff --git a/cmd/api-response.go b/cmd/api-response.go index 7f0641bb2..bc95cab60 100644 --- a/cmd/api-response.go +++ b/cmd/api-response.go @@ -166,12 +166,13 @@ type ListBucketsResponse struct { // Upload container for in progress multipart upload type Upload struct { - Key string - UploadID string `xml:"UploadId"` - Initiator Initiator - Owner Owner - StorageClass string - Initiated string + Key string + UploadID string `xml:"UploadId"` + Initiator Initiator + Owner Owner + StorageClass string + Initiated string + HealUploadInfo *HealObjectInfo `xml:"HealObjectInfo,omitempty"` } // CommonPrefix container for prefix response in ListObjectsResponse @@ -488,6 +489,7 @@ func generateListMultipartUploadsResponse(bucket string, multipartsInfo ListMult newUpload.UploadID = upload.UploadID newUpload.Key = upload.Object newUpload.Initiated = upload.Initiated.UTC().Format(timeFormatAMZLong) + newUpload.HealUploadInfo = upload.HealUploadInfo listMultipartUploadsResponse.Uploads[index] = newUpload } return listMultipartUploadsResponse diff --git a/cmd/auth-handler.go b/cmd/auth-handler.go index a48989f12..a979ae961 100644 --- a/cmd/auth-handler.go +++ b/cmd/auth-handler.go @@ -63,7 +63,9 @@ func isRequestPostPolicySignatureV4(r *http.Request) bool { // Verify if the request has AWS Streaming Signature Version '4'. This is only valid for 'PUT' operation. func isRequestSignStreamingV4(r *http.Request) bool { - return r.Header.Get("x-amz-content-sha256") == streamingContentSHA256 && r.Method == httpPUT + return r.Header.Get("x-amz-content-sha256") == streamingContentSHA256 && + r.Header.Get("content-encoding") == streamingContentEncoding && + r.Method == httpPUT } // Authorization type. diff --git a/cmd/auth-handler_test.go b/cmd/auth-handler_test.go index fbb78d4e2..bd6cafd8e 100644 --- a/cmd/auth-handler_test.go +++ b/cmd/auth-handler_test.go @@ -43,6 +43,7 @@ func TestGetRequestAuthType(t *testing.T) { Header: http.Header{ "Authorization": []string{"AWS4-HMAC-SHA256 "}, "X-Amz-Content-Sha256": []string{streamingContentSHA256}, + "Content-Encoding": []string{streamingContentEncoding}, }, Method: "PUT", }, @@ -315,7 +316,11 @@ func TestIsReqAuthenticated(t *testing.T) { } defer removeAll(path) - creds := newCredentialWithKeys("myuser", "mypassword") + creds, err := createCredential("myuser", "mypassword") + if err != nil { + t.Fatalf("unable create credential, %s", err) + } + serverConfig.SetCredential(creds) // List of test cases for validating http request authentication. diff --git a/cmd/auth-rpc-client.go b/cmd/auth-rpc-client.go index 74c7815b6..c8385badf 100644 --- a/cmd/auth-rpc-client.go +++ b/cmd/auth-rpc-client.go @@ -111,13 +111,13 @@ func (authClient *AuthRPCClient) Login() (err error) { // call makes a RPC call after logs into the server. func (authClient *AuthRPCClient) call(serviceMethod string, args interface { SetAuthToken(authToken string) - SetRequestTime(requestTime time.Time) }, reply interface{}) (err error) { // On successful login, execute RPC call. if err = authClient.Login(); err == nil { + authClient.Lock() // Set token and timestamp before the rpc call. args.SetAuthToken(authClient.authToken) - args.SetRequestTime(time.Now().UTC()) + authClient.Unlock() // Do RPC call. err = authClient.rpcClient.Call(serviceMethod, args, reply) @@ -128,7 +128,6 @@ func (authClient *AuthRPCClient) call(serviceMethod string, args interface { // Call executes RPC call till success or globalAuthRPCRetryThreshold on ErrShutdown. func (authClient *AuthRPCClient) Call(serviceMethod string, args interface { SetAuthToken(authToken string) - SetRequestTime(requestTime time.Time) }, reply interface{}) (err error) { // Done channel is used to close any lingering retry routine, as soon diff --git a/cmd/azure-anonymous.go b/cmd/azure-anonymous.go new file mode 100644 index 000000000..72bb6bc9b --- /dev/null +++ b/cmd/azure-anonymous.go @@ -0,0 +1,185 @@ +/* + * Minio Cloud Storage, (C) 2017 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cmd + +import ( + "encoding/xml" + "fmt" + "io" + "io/ioutil" + "net/http" + "net/url" + "strconv" + "time" + + "github.com/Azure/azure-sdk-for-go/storage" +) + +// AnonGetBucketInfo - Get bucket metadata from azure anonymously. +func (a AzureObjects) AnonGetBucketInfo(bucket string) (bucketInfo BucketInfo, err error) { + url, err := url.Parse(a.client.GetBlobURL(bucket, "")) + if err != nil { + return bucketInfo, azureToObjectError(traceError(err)) + } + url.RawQuery = "restype=container" + resp, err := http.Head(url.String()) + if err != nil { + return bucketInfo, azureToObjectError(traceError(err), bucket) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return bucketInfo, azureToObjectError(traceError(anonErrToObjectErr(resp.StatusCode, bucket)), bucket) + } + + t, err := time.Parse(time.RFC1123, resp.Header.Get("Last-Modified")) + if err != nil { + return bucketInfo, traceError(err) + } + bucketInfo = BucketInfo{ + Name: bucket, + Created: t, + } + return bucketInfo, nil +} + +// AnonGetObject - SendGET request without authentication. +// This is needed when clients send GET requests on objects that can be downloaded without auth. +func (a AzureObjects) AnonGetObject(bucket, object string, startOffset int64, length int64, writer io.Writer) (err error) { + url := a.client.GetBlobURL(bucket, object) + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return azureToObjectError(traceError(err), bucket, object) + } + + if length > 0 && startOffset > 0 { + req.Header.Add("Range", fmt.Sprintf("bytes=%d-%d", startOffset, startOffset+length-1)) + } else if startOffset > 0 { + req.Header.Add("Range", fmt.Sprintf("bytes=%d-", startOffset)) + } + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return azureToObjectError(traceError(err), bucket, object) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusPartialContent && resp.StatusCode != http.StatusOK { + return azureToObjectError(traceError(anonErrToObjectErr(resp.StatusCode, bucket, object)), bucket, object) + } + + _, err = io.Copy(writer, resp.Body) + return traceError(err) +} + +// AnonGetObjectInfo - Send HEAD request without authentication and convert the +// result to ObjectInfo. +func (a AzureObjects) AnonGetObjectInfo(bucket, object string) (objInfo ObjectInfo, err error) { + resp, err := http.Head(a.client.GetBlobURL(bucket, object)) + if err != nil { + return objInfo, azureToObjectError(traceError(err), bucket, object) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return objInfo, azureToObjectError(traceError(anonErrToObjectErr(resp.StatusCode, bucket, object)), bucket, object) + } + + var contentLength int64 + contentLengthStr := resp.Header.Get("Content-Length") + if contentLengthStr != "" { + contentLength, err = strconv.ParseInt(contentLengthStr, 0, 64) + if err != nil { + return objInfo, azureToObjectError(traceError(errUnexpected), bucket, object) + } + } + + t, err := time.Parse(time.RFC1123, resp.Header.Get("Last-Modified")) + if err != nil { + return objInfo, traceError(err) + } + + objInfo.ModTime = t + objInfo.Bucket = bucket + objInfo.UserDefined = make(map[string]string) + if resp.Header.Get("Content-Encoding") != "" { + objInfo.UserDefined["Content-Encoding"] = resp.Header.Get("Content-Encoding") + } + objInfo.UserDefined["Content-Type"] = resp.Header.Get("Content-Type") + objInfo.MD5Sum = resp.Header.Get("Etag") + objInfo.ModTime = t + objInfo.Name = object + objInfo.Size = contentLength + return +} + +// AnonListObjects - Use Azure equivalent ListBlobs. +func (a AzureObjects) AnonListObjects(bucket, prefix, marker, delimiter string, maxKeys int) (result ListObjectsInfo, err error) { + params := storage.ListBlobsParameters{ + Prefix: prefix, + Marker: marker, + Delimiter: delimiter, + MaxResults: uint(maxKeys), + } + + q := azureListBlobsGetParameters(params) + q.Set("restype", "container") + q.Set("comp", "list") + + url, err := url.Parse(a.client.GetBlobURL(bucket, "")) + if err != nil { + return result, azureToObjectError(traceError(err)) + } + url.RawQuery = q.Encode() + + resp, err := http.Get(url.String()) + if err != nil { + return result, azureToObjectError(traceError(err)) + } + defer resp.Body.Close() + + var listResp storage.BlobListResponse + + data, err := ioutil.ReadAll(resp.Body) + if err != nil { + return result, azureToObjectError(traceError(err)) + } + err = xml.Unmarshal(data, &listResp) + if err != nil { + return result, azureToObjectError(traceError(err)) + } + + result.IsTruncated = listResp.NextMarker != "" + result.NextMarker = listResp.NextMarker + for _, object := range listResp.Blobs { + t, e := time.Parse(time.RFC1123, object.Properties.LastModified) + if e != nil { + continue + } + result.Objects = append(result.Objects, ObjectInfo{ + Bucket: bucket, + Name: object.Name, + ModTime: t, + Size: object.Properties.ContentLength, + MD5Sum: object.Properties.Etag, + ContentType: object.Properties.ContentType, + ContentEncoding: object.Properties.ContentEncoding, + }) + } + result.Prefixes = listResp.BlobPrefixes + return result, nil +} diff --git a/cmd/azure-unsupported.go b/cmd/azure-unsupported.go new file mode 100644 index 000000000..a6d821dc4 --- /dev/null +++ b/cmd/azure-unsupported.go @@ -0,0 +1,43 @@ +/* + * Minio Cloud Storage, (C) 2017 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cmd + +// HealBucket - Not relevant. +func (a AzureObjects) HealBucket(bucket string) error { + return traceError(NotImplemented{}) +} + +// ListBucketsHeal - Not relevant. +func (a AzureObjects) ListBucketsHeal() (buckets []BucketInfo, err error) { + return nil, traceError(NotImplemented{}) +} + +// HealObject - Not relevant. +func (a AzureObjects) HealObject(bucket, object string) error { + return traceError(NotImplemented{}) +} + +// ListObjectsHeal - Not relevant. +func (a AzureObjects) ListObjectsHeal(bucket, prefix, marker, delimiter string, maxKeys int) (ListObjectsInfo, error) { + return ListObjectsInfo{}, traceError(NotImplemented{}) +} + +// ListUploadsHeal - Not relevant. +func (a AzureObjects) ListUploadsHeal(bucket, prefix, marker, uploadIDMarker, + delimiter string, maxUploads int) (ListMultipartsInfo, error) { + return ListMultipartsInfo{}, traceError(NotImplemented{}) +} diff --git a/cmd/azure.go b/cmd/azure.go new file mode 100644 index 000000000..5b3eebb2e --- /dev/null +++ b/cmd/azure.go @@ -0,0 +1,635 @@ +/* + * Minio Cloud Storage, (C) 2017 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cmd + +import ( + "encoding/base64" + "encoding/hex" + "fmt" + "hash" + "io" + "net/http" + "net/url" + "strconv" + "strings" + "sync" + "time" + + "github.com/Azure/azure-sdk-for-go/storage" + "github.com/minio/minio-go/pkg/policy" + "github.com/minio/sha256-simd" +) + +const globalAzureAPIVersion = "2016-05-31" + +// To store metadata during NewMultipartUpload which will be used after +// CompleteMultipartUpload to call SetBlobMetadata. +type azureMultipartMetaInfo struct { + meta map[string]map[string]string + *sync.Mutex +} + +// Return metadata map of the multipart object. +func (a *azureMultipartMetaInfo) get(key string) map[string]string { + a.Lock() + defer a.Unlock() + return a.meta[key] +} + +// Set metadata map for the multipart object. +func (a *azureMultipartMetaInfo) set(key string, value map[string]string) { + a.Lock() + defer a.Unlock() + a.meta[key] = value +} + +// Delete metadata map for the multipart object. +func (a *azureMultipartMetaInfo) del(key string) { + a.Lock() + defer a.Unlock() + delete(a.meta, key) +} + +// AzureObjects - Implements Object layer for Azure blob storage. +type AzureObjects struct { + client storage.BlobStorageClient // Azure sdk client + metaInfo azureMultipartMetaInfo +} + +// Convert azure errors to minio object layer errors. +func azureToObjectError(err error, params ...string) error { + if err == nil { + return nil + } + + e, ok := err.(*Error) + if !ok { + // Code should be fixed if this function is called without doing traceError() + // Else handling different situations in this function makes this function complicated. + errorIf(err, "Expected type *Error") + return err + } + + err = e.e + bucket := "" + object := "" + if len(params) >= 1 { + bucket = params[0] + } + if len(params) == 2 { + object = params[1] + } + + azureErr, ok := err.(storage.AzureStorageServiceError) + if !ok { + // We don't interpret non Azure errors. As azure errors will + // have StatusCode to help to convert to object errors. + return e + } + + switch azureErr.Code { + case "ContainerAlreadyExists": + err = BucketExists{Bucket: bucket} + case "InvalidResourceName": + err = BucketNameInvalid{Bucket: bucket} + default: + switch azureErr.StatusCode { + case http.StatusNotFound: + if object != "" { + err = ObjectNotFound{bucket, object} + } else { + err = BucketNotFound{Bucket: bucket} + } + case http.StatusBadRequest: + err = BucketNameInvalid{Bucket: bucket} + } + } + e.e = err + return e +} + +// Inits azure blob storage client and returns AzureObjects. +func newAzureLayer(account, key string) (GatewayLayer, error) { + useHTTPS := true + c, err := storage.NewClient(account, key, storage.DefaultBaseURL, globalAzureAPIVersion, useHTTPS) + if err != nil { + return AzureObjects{}, err + } + return &AzureObjects{ + client: c.GetBlobService(), + metaInfo: azureMultipartMetaInfo{ + meta: make(map[string]map[string]string), + Mutex: &sync.Mutex{}, + }, + }, nil +} + +// Shutdown - save any gateway metadata to disk +// if necessary and reload upon next restart. +func (a AzureObjects) Shutdown() error { + // TODO + return nil +} + +// StorageInfo - Not relevant to Azure backend. +func (a AzureObjects) StorageInfo() StorageInfo { + return StorageInfo{} +} + +// MakeBucket - Create a new container on azure backend. +func (a AzureObjects) MakeBucket(bucket string) error { + err := a.client.CreateContainer(bucket, storage.ContainerAccessTypePrivate) + return azureToObjectError(traceError(err), bucket) +} + +// GetBucketInfo - Get bucket metadata.. +func (a AzureObjects) GetBucketInfo(bucket string) (BucketInfo, error) { + // Azure does not have an equivalent call, hence use ListContainers. + resp, err := a.client.ListContainers(storage.ListContainersParameters{ + Prefix: bucket, + }) + if err != nil { + return BucketInfo{}, azureToObjectError(traceError(err), bucket) + } + for _, container := range resp.Containers { + if container.Name == bucket { + t, e := time.Parse(time.RFC1123, container.Properties.LastModified) + if e == nil { + return BucketInfo{ + Name: bucket, + Created: t, + }, nil + } // else continue + } + } + return BucketInfo{}, traceError(BucketNotFound{Bucket: bucket}) +} + +// ListBuckets - Lists all azure containers, uses Azure equivalent ListContainers. +func (a AzureObjects) ListBuckets() (buckets []BucketInfo, err error) { + resp, err := a.client.ListContainers(storage.ListContainersParameters{}) + if err != nil { + return nil, azureToObjectError(traceError(err)) + } + for _, container := range resp.Containers { + t, e := time.Parse(time.RFC1123, container.Properties.LastModified) + if e != nil { + return nil, traceError(e) + } + buckets = append(buckets, BucketInfo{ + Name: container.Name, + Created: t, + }) + } + return buckets, nil +} + +// DeleteBucket - delete a container on azure, uses Azure equivalent DeleteContainer. +func (a AzureObjects) DeleteBucket(bucket string) error { + return azureToObjectError(traceError(a.client.DeleteContainer(bucket)), bucket) +} + +// ListObjects - lists all blobs on azure with in a container filtered by prefix +// and marker, uses Azure equivalent ListBlobs. +func (a AzureObjects) ListObjects(bucket, prefix, marker, delimiter string, maxKeys int) (result ListObjectsInfo, err error) { + resp, err := a.client.ListBlobs(bucket, storage.ListBlobsParameters{ + Prefix: prefix, + Marker: marker, + Delimiter: delimiter, + MaxResults: uint(maxKeys), + }) + if err != nil { + return result, azureToObjectError(traceError(err), bucket, prefix) + } + result.IsTruncated = resp.NextMarker != "" + result.NextMarker = resp.NextMarker + for _, object := range resp.Blobs { + t, e := time.Parse(time.RFC1123, object.Properties.LastModified) + if e != nil { + continue + } + result.Objects = append(result.Objects, ObjectInfo{ + Bucket: bucket, + Name: object.Name, + ModTime: t, + Size: object.Properties.ContentLength, + MD5Sum: canonicalizeETag(object.Properties.Etag), + ContentType: object.Properties.ContentType, + ContentEncoding: object.Properties.ContentEncoding, + }) + } + result.Prefixes = resp.BlobPrefixes + return result, nil +} + +// GetObject - reads an object from azure. Supports additional +// parameters like offset and length which are synonymous with +// HTTP Range requests. +// +// startOffset indicates the starting read location of the object. +// length indicates the total length of the object. +func (a AzureObjects) GetObject(bucket, object string, startOffset int64, length int64, writer io.Writer) error { + byteRange := fmt.Sprintf("%d-", startOffset) + if length > 0 && startOffset > 0 { + byteRange = fmt.Sprintf("%d-%d", startOffset, startOffset+length-1) + } + + var rc io.ReadCloser + var err error + if startOffset == 0 && length == 0 { + rc, err = a.client.GetBlob(bucket, object) + } else { + rc, err = a.client.GetBlobRange(bucket, object, byteRange, nil) + } + if err != nil { + return azureToObjectError(traceError(err), bucket, object) + } + _, err = io.Copy(writer, rc) + rc.Close() + return traceError(err) +} + +// GetObjectInfo - reads blob metadata properties and replies back ObjectInfo, +// uses zure equivalent GetBlobProperties. +func (a AzureObjects) GetObjectInfo(bucket, object string) (objInfo ObjectInfo, err error) { + prop, err := a.client.GetBlobProperties(bucket, object) + if err != nil { + return objInfo, azureToObjectError(traceError(err), bucket, object) + } + t, err := time.Parse(time.RFC1123, prop.LastModified) + if err != nil { + return objInfo, traceError(err) + } + objInfo = ObjectInfo{ + Bucket: bucket, + UserDefined: make(map[string]string), + MD5Sum: canonicalizeETag(prop.Etag), + ModTime: t, + Name: object, + Size: prop.ContentLength, + } + if prop.ContentEncoding != "" { + objInfo.UserDefined["Content-Encoding"] = prop.ContentEncoding + } + objInfo.UserDefined["Content-Type"] = prop.ContentType + return objInfo, nil +} + +// Canonicalize the metadata headers, without this azure-sdk calculates +// incorrect signature. This attempt to canonicalize is to convert +// any HTTP header which is of form say `accept-encoding` should be +// converted to `Accept-Encoding` in its canonical form. +func canonicalMetadata(metadata map[string]string) (canonical map[string]string) { + canonical = make(map[string]string) + for k, v := range metadata { + canonical[http.CanonicalHeaderKey(k)] = v + } + return canonical +} + +// PutObject - Create a new blob with the incoming data, +// uses Azure equivalent CreateBlockBlobFromReader. +func (a AzureObjects) PutObject(bucket, object string, size int64, data io.Reader, metadata map[string]string, sha256sum string) (objInfo ObjectInfo, err error) { + var sha256Writer hash.Hash + teeReader := data + if sha256sum != "" { + sha256Writer = sha256.New() + teeReader = io.TeeReader(data, sha256Writer) + } + + delete(metadata, "md5Sum") + + err = a.client.CreateBlockBlobFromReader(bucket, object, uint64(size), teeReader, canonicalMetadata(metadata)) + if err != nil { + return objInfo, azureToObjectError(traceError(err), bucket, object) + } + + if sha256sum != "" { + newSHA256sum := hex.EncodeToString(sha256Writer.Sum(nil)) + if newSHA256sum != sha256sum { + a.client.DeleteBlob(bucket, object, nil) + return ObjectInfo{}, traceError(SHA256Mismatch{}) + } + } + + return a.GetObjectInfo(bucket, object) +} + +// CopyObject - Copies a blob from source container to destination container. +// Uses Azure equivalent CopyBlob API. +func (a AzureObjects) CopyObject(srcBucket, srcObject, destBucket, destObject string, metadata map[string]string) (objInfo ObjectInfo, err error) { + err = a.client.CopyBlob(destBucket, destObject, a.client.GetBlobURL(srcBucket, srcObject)) + if err != nil { + return objInfo, azureToObjectError(traceError(err), srcBucket, srcObject) + } + return a.GetObjectInfo(destBucket, destObject) +} + +// DeleteObject - Deletes a blob on azure container, uses Azure +// equivalent DeleteBlob API. +func (a AzureObjects) DeleteObject(bucket, object string) error { + err := a.client.DeleteBlob(bucket, object, nil) + if err != nil { + return azureToObjectError(traceError(err), bucket, object) + } + return nil +} + +// ListMultipartUploads - Incomplete implementation, for now just return the prefix if it is an incomplete upload. +// FIXME: Full ListMultipartUploads is not supported yet. It is supported just enough to help our client libs to +// support re-uploads. a.client.ListBlobs() can be made to return entries which include uncommitted blobs using +// which we need to filter out the committed blobs to get the list of uncommitted blobs. +func (a AzureObjects) ListMultipartUploads(bucket, prefix, keyMarker, uploadIDMarker, delimiter string, maxUploads int) (result ListMultipartsInfo, err error) { + result.MaxUploads = maxUploads + result.Prefix = prefix + result.Delimiter = delimiter + meta := a.metaInfo.get(prefix) + if meta == nil { + // In case minio was restarted after NewMultipartUpload and before CompleteMultipartUpload we expect + // the client to do a fresh upload so that any metadata like content-type are sent again in the + // NewMultipartUpload. + return result, nil + } + result.Uploads = []uploadMetadata{{prefix, prefix, time.Now().UTC(), "", nil}} + return result, nil +} + +// NewMultipartUpload - Use Azure equivalent CreateBlockBlob. +func (a AzureObjects) NewMultipartUpload(bucket, object string, metadata map[string]string) (uploadID string, err error) { + // Azure doesn't return a unique upload ID and we use object name in place of it. Azure allows multiple uploads to + // co-exist as long as the user keeps the blocks uploaded (in block blobs) unique amongst concurrent upload attempts. + // Each concurrent client, keeps its own blockID list which it can commit. + uploadID = object + if metadata == nil { + // Store an empty map as a placeholder else ListObjectParts/PutObjectPart will not work properly. + metadata = make(map[string]string) + } else { + metadata = canonicalMetadata(metadata) + } + a.metaInfo.set(uploadID, metadata) + return uploadID, nil +} + +// CopyObjectPart - Not implemented. +func (a AzureObjects) CopyObjectPart(srcBucket, srcObject, destBucket, destObject string, uploadID string, partID int, startOffset int64, length int64) (info PartInfo, err error) { + return info, traceError(NotImplemented{}) +} + +// Encode partID+md5Hex to a blockID. +func azureGetBlockID(partID int, md5Hex string) string { + return base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%.5d.%s", partID, md5Hex))) +} + +// Decode blockID to partID+md5Hex. +func azureParseBlockID(blockID string) (int, string, error) { + idByte, err := base64.StdEncoding.DecodeString(blockID) + if err != nil { + return 0, "", traceError(err) + } + idStr := string(idByte) + splitRes := strings.Split(idStr, ".") + if len(splitRes) != 2 { + return 0, "", traceError(errUnexpected) + } + partID, err := strconv.Atoi(splitRes[0]) + if err != nil { + return 0, "", traceError(err) + } + return partID, splitRes[1], nil +} + +// PutObjectPart - Use Azure equivalent PutBlockWithLength. +func (a AzureObjects) PutObjectPart(bucket, object, uploadID string, partID int, size int64, data io.Reader, md5Hex string, sha256sum string) (info PartInfo, err error) { + if meta := a.metaInfo.get(uploadID); meta == nil { + return info, traceError(InvalidUploadID{}) + } + var sha256Writer hash.Hash + if sha256sum != "" { + sha256Writer = sha256.New() + } + + teeReader := io.TeeReader(data, sha256Writer) + + id := azureGetBlockID(partID, md5Hex) + err = a.client.PutBlockWithLength(bucket, object, id, uint64(size), teeReader, nil) + if err != nil { + return info, azureToObjectError(traceError(err), bucket, object) + } + + if sha256sum != "" { + newSHA256sum := hex.EncodeToString(sha256Writer.Sum(nil)) + if newSHA256sum != sha256sum { + return PartInfo{}, traceError(SHA256Mismatch{}) + } + } + + info.PartNumber = partID + info.ETag = md5Hex + info.LastModified = time.Now().UTC() + info.Size = size + return info, nil +} + +// ListObjectParts - Use Azure equivalent GetBlockList. +func (a AzureObjects) ListObjectParts(bucket, object, uploadID string, partNumberMarker int, maxParts int) (result ListPartsInfo, err error) { + result.Bucket = bucket + result.Object = object + result.UploadID = uploadID + result.MaxParts = maxParts + + if meta := a.metaInfo.get(uploadID); meta == nil { + return result, nil + } + resp, err := a.client.GetBlockList(bucket, object, storage.BlockListTypeUncommitted) + if err != nil { + return result, azureToObjectError(traceError(err), bucket, object) + } + tmpMaxParts := 0 + partCount := 0 // Used for figuring out IsTruncated. + nextPartNumberMarker := 0 + for _, part := range resp.UncommittedBlocks { + if tmpMaxParts == maxParts { + // Also takes care of the case if maxParts = 0 + break + } + partCount++ + partID, md5Hex, err := azureParseBlockID(part.Name) + if err != nil { + return result, err + } + if partID <= partNumberMarker { + continue + } + result.Parts = append(result.Parts, PartInfo{ + partID, + time.Now().UTC(), + md5Hex, + part.Size, + }) + tmpMaxParts++ + nextPartNumberMarker = partID + } + if partCount < len(resp.UncommittedBlocks) { + result.IsTruncated = true + result.NextPartNumberMarker = nextPartNumberMarker + } + + return result, nil +} + +// AbortMultipartUpload - Not Implemented. +// There is no corresponding API in azure to abort an incomplete upload. The uncommmitted blocks +// gets deleted after one week. +func (a AzureObjects) AbortMultipartUpload(bucket, object, uploadID string) error { + a.metaInfo.del(uploadID) + return nil +} + +// CompleteMultipartUpload - Use Azure equivalent PutBlockList. +func (a AzureObjects) CompleteMultipartUpload(bucket, object, uploadID string, uploadedParts []completePart) (objInfo ObjectInfo, err error) { + meta := a.metaInfo.get(uploadID) + if meta == nil { + return objInfo, traceError(InvalidUploadID{uploadID}) + } + var blocks []storage.Block + for _, part := range uploadedParts { + blocks = append(blocks, storage.Block{ + ID: azureGetBlockID(part.PartNumber, part.ETag), + Status: storage.BlockStatusUncommitted, + }) + } + err = a.client.PutBlockList(bucket, object, blocks) + if err != nil { + return objInfo, azureToObjectError(traceError(err), bucket, object) + } + if len(meta) > 0 { + prop := storage.BlobHeaders{ + ContentMD5: meta["Content-Md5"], + ContentLanguage: meta["Content-Language"], + ContentEncoding: meta["Content-Encoding"], + ContentType: meta["Content-Type"], + CacheControl: meta["Cache-Control"], + } + err = a.client.SetBlobProperties(bucket, object, prop) + if err != nil { + return objInfo, azureToObjectError(traceError(err), bucket, object) + } + } + a.metaInfo.del(uploadID) + return a.GetObjectInfo(bucket, object) +} + +func anonErrToObjectErr(statusCode int, params ...string) error { + bucket := "" + object := "" + if len(params) >= 1 { + bucket = params[0] + } + if len(params) == 2 { + object = params[1] + } + + switch statusCode { + case http.StatusNotFound: + if object != "" { + return ObjectNotFound{bucket, object} + } + return BucketNotFound{Bucket: bucket} + case http.StatusBadRequest: + if object != "" { + return ObjectNameInvalid{bucket, object} + } + return BucketNameInvalid{Bucket: bucket} + } + return errUnexpected +} + +// Copied from github.com/Azure/azure-sdk-for-go/storage/blob.go +func azureListBlobsGetParameters(p storage.ListBlobsParameters) url.Values { + out := url.Values{} + + if p.Prefix != "" { + out.Set("prefix", p.Prefix) + } + if p.Delimiter != "" { + out.Set("delimiter", p.Delimiter) + } + if p.Marker != "" { + out.Set("marker", p.Marker) + } + if p.Include != "" { + out.Set("include", p.Include) + } + if p.MaxResults != 0 { + out.Set("maxresults", fmt.Sprintf("%v", p.MaxResults)) + } + if p.Timeout != 0 { + out.Set("timeout", fmt.Sprintf("%v", p.Timeout)) + } + + return out +} + +// SetBucketPolicies - Azure supports three types of container policies: +// storage.ContainerAccessTypeContainer - readonly in minio terminology +// storage.ContainerAccessTypeBlob - readonly without listing in minio terminology +// storage.ContainerAccessTypePrivate - none in minio terminology +// As the common denominator for minio and azure is readonly and none, we support +// these two policies at the bucket level. +func (a AzureObjects) SetBucketPolicies(bucket string, policies []BucketAccessPolicy) error { + prefix := bucket + "/*" // For all objects inside the bucket. + if len(policies) != 1 { + return traceError(NotImplemented{}) + } + if policies[0].Prefix != prefix { + return traceError(NotImplemented{}) + } + if policies[0].Policy != policy.BucketPolicyReadOnly { + return traceError(NotImplemented{}) + } + perm := storage.ContainerPermissions{ + AccessType: storage.ContainerAccessTypeContainer, + AccessPolicies: nil, + } + err := a.client.SetContainerPermissions(bucket, perm, 0, "") + return azureToObjectError(traceError(err), bucket) +} + +// GetBucketPolicies - Get the container ACL and convert it to canonical []bucketAccessPolicy +func (a AzureObjects) GetBucketPolicies(bucket string) ([]BucketAccessPolicy, error) { + perm, err := a.client.GetContainerPermissions(bucket, 0, "") + if err != nil { + return nil, azureToObjectError(traceError(err), bucket) + } + switch perm.AccessType { + case storage.ContainerAccessTypePrivate: + return nil, nil + case storage.ContainerAccessTypeContainer: + return []BucketAccessPolicy{{"", policy.BucketPolicyReadOnly}}, nil + } + return nil, azureToObjectError(traceError(NotImplemented{})) +} + +// DeleteBucketPolicies - Set the container ACL to "private" +func (a AzureObjects) DeleteBucketPolicies(bucket string) error { + perm := storage.ContainerPermissions{ + AccessType: storage.ContainerAccessTypePrivate, + AccessPolicies: nil, + } + err := a.client.SetContainerPermissions(bucket, perm, 0, "") + return azureToObjectError(traceError(err)) +} diff --git a/cmd/azure_test.go b/cmd/azure_test.go new file mode 100644 index 000000000..b7d99debe --- /dev/null +++ b/cmd/azure_test.go @@ -0,0 +1,142 @@ +/* + * Minio Cloud Storage, (C) 2017 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cmd + +import ( + "net/http" + "reflect" + "testing" + + "github.com/Azure/azure-sdk-for-go/storage" +) + +// Test canonical metadata. +func TestCanonicalMetadata(t *testing.T) { + metadata := map[string]string{ + "accept-encoding": "gzip", + "content-encoding": "gzip", + } + expectedCanonicalM := map[string]string{ + "Accept-Encoding": "gzip", + "Content-Encoding": "gzip", + } + actualCanonicalM := canonicalMetadata(metadata) + if !reflect.DeepEqual(actualCanonicalM, expectedCanonicalM) { + t.Fatalf("Test failed, expected %#v, got %#v", expectedCanonicalM, actualCanonicalM) + } +} + +// Add tests for azure to object error. +func TestAzureToObjectError(t *testing.T) { + testCases := []struct { + actualErr error + expectedErr error + bucket, object string + }{ + { + nil, nil, "", "", + }, + { + traceError(errUnexpected), errUnexpected, "", "", + }, + { + traceError(errUnexpected), traceError(errUnexpected), "", "", + }, + { + traceError(storage.AzureStorageServiceError{ + Code: "ContainerAlreadyExists", + }), BucketExists{Bucket: "bucket"}, "bucket", "", + }, + { + traceError(storage.AzureStorageServiceError{ + Code: "InvalidResourceName", + }), BucketNameInvalid{Bucket: "bucket."}, "bucket.", "", + }, + { + traceError(storage.AzureStorageServiceError{ + StatusCode: http.StatusNotFound, + }), ObjectNotFound{ + Bucket: "bucket", + Object: "object", + }, "bucket", "object", + }, + { + traceError(storage.AzureStorageServiceError{ + StatusCode: http.StatusNotFound, + }), BucketNotFound{Bucket: "bucket"}, "bucket", "", + }, + { + traceError(storage.AzureStorageServiceError{ + StatusCode: http.StatusBadRequest, + }), BucketNameInvalid{Bucket: "bucket."}, "bucket.", "", + }, + } + for i, testCase := range testCases { + err := azureToObjectError(testCase.actualErr, testCase.bucket, testCase.object) + if err != nil { + if err.Error() != testCase.expectedErr.Error() { + t.Errorf("Test %d: Expected error %s, got %s", i+1, testCase.expectedErr, err) + } + } + } +} + +// Test azureGetBlockID(). +func TestAzureGetBlockID(t *testing.T) { + testCases := []struct { + partID int + md5 string + blockID string + }{ + {1, "d41d8cd98f00b204e9800998ecf8427e", "MDAwMDEuZDQxZDhjZDk4ZjAwYjIwNGU5ODAwOTk4ZWNmODQyN2U="}, + {2, "a7fb6b7b36ee4ed66b5546fac4690273", "MDAwMDIuYTdmYjZiN2IzNmVlNGVkNjZiNTU0NmZhYzQ2OTAyNzM="}, + } + for _, test := range testCases { + blockID := azureGetBlockID(test.partID, test.md5) + if blockID != test.blockID { + t.Fatalf("%s is not equal to %s", blockID, test.blockID) + } + } +} + +// Test azureParseBlockID(). +func TestAzureParseBlockID(t *testing.T) { + testCases := []struct { + partID int + md5 string + blockID string + }{ + {1, "d41d8cd98f00b204e9800998ecf8427e", "MDAwMDEuZDQxZDhjZDk4ZjAwYjIwNGU5ODAwOTk4ZWNmODQyN2U="}, + {2, "a7fb6b7b36ee4ed66b5546fac4690273", "MDAwMDIuYTdmYjZiN2IzNmVlNGVkNjZiNTU0NmZhYzQ2OTAyNzM="}, + } + for _, test := range testCases { + partID, md5, err := azureParseBlockID(test.blockID) + if err != nil { + t.Fatal(err) + } + if partID != test.partID { + t.Fatalf("%d not equal to %d", partID, test.partID) + } + if md5 != test.md5 { + t.Fatalf("%s not equal to %s", md5, test.md5) + } + } + _, _, err := azureParseBlockID("junk") + if err == nil { + t.Fatal("Expected azureParseBlockID() to return error") + } +} diff --git a/cmd/benchmark-utils_test.go b/cmd/benchmark-utils_test.go index bcd231568..0bd8f3c3d 100644 --- a/cmd/benchmark-utils_test.go +++ b/cmd/benchmark-utils_test.go @@ -28,25 +28,9 @@ import ( humanize "github.com/dustin/go-humanize" ) -// Prepare benchmark backend +// Prepare XL/FS backend for benchmark. func prepareBenchmarkBackend(instanceType string) (ObjectLayer, []string, error) { - switch instanceType { - // Total number of disks for FS backend is set to 1. - case FSTestStr: - obj, disk, err := prepareFS() - if err != nil { - return nil, nil, err - } - return obj, []string{disk}, nil - // Total number of disks for XL backend is set to 16. - case XLTestStr: - return prepareXL() - } - obj, disk, err := prepareFS() - if err != nil { - return nil, nil, err - } - return obj, []string{disk}, nil + return prepareTestBackend(instanceType) } // Benchmark utility functions for ObjectLayer.PutObject(). diff --git a/cmd/browser-peer-rpc.go b/cmd/browser-peer-rpc.go index 398b907bf..4602cba27 100644 --- a/cmd/browser-peer-rpc.go +++ b/cmd/browser-peer-rpc.go @@ -1,5 +1,5 @@ /* - * Minio Cloud Storage, (C) 2016 Minio, Inc. + * Minio Cloud Storage, (C) 2016, 2017 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ package cmd import ( + "fmt" "path" "sync" "time" @@ -63,8 +64,8 @@ func (br *browserPeerAPIHandlers) SetAuthPeer(args SetAuthPeerArgs, reply *AuthR return err } - if err := validateAuthKeys(args.Creds.AccessKey, args.Creds.SecretKey); err != nil { - return err + if !args.Creds.IsValid() { + return fmt.Errorf("Invalid credential passed") } // Update credentials in memory @@ -111,7 +112,7 @@ func updateCredsOnPeers(creds credential) map[string]error { secretKey: serverCred.SecretKey, serverAddr: peers[ix], secureConn: globalIsSSL, - serviceEndpoint: path.Join(reservedBucket, browserPeerPath), + serviceEndpoint: path.Join(minioReservedBucketPath, browserPeerPath), serviceName: "BrowserPeer", }) diff --git a/cmd/browser-peer-rpc_test.go b/cmd/browser-peer-rpc_test.go index 5554c4f32..2bea40e9c 100644 --- a/cmd/browser-peer-rpc_test.go +++ b/cmd/browser-peer-rpc_test.go @@ -36,7 +36,7 @@ func (s *TestRPCBrowserPeerSuite) SetUpSuite(c *testing.T) { serverAddr: s.testServer.Server.Listener.Addr().String(), accessKey: s.testServer.AccessKey, secretKey: s.testServer.SecretKey, - serviceEndpoint: path.Join(reservedBucket, browserPeerPath), + serviceEndpoint: path.Join(minioReservedBucketPath, browserPeerPath), serviceName: "BrowserPeer", } } @@ -63,9 +63,9 @@ func TestBrowserPeerRPC(t *testing.T) { // Tests for browser peer rpc. func (s *TestRPCBrowserPeerSuite) testBrowserPeerRPC(t *testing.T) { // Construct RPC call arguments. - creds := credential{ - AccessKey: "abcd1", - SecretKey: "abcd1234", + creds, err := createCredential("abcd1", "abcd1234") + if err != nil { + t.Fatalf("unable to create credential. %v", err) } // Validate for invalid token. @@ -73,7 +73,7 @@ func (s *TestRPCBrowserPeerSuite) testBrowserPeerRPC(t *testing.T) { args.AuthToken = "garbage" rclient := newRPCClient(s.testAuthConf.serverAddr, s.testAuthConf.serviceEndpoint, false) defer rclient.Close() - err := rclient.Call("BrowserPeer.SetAuthPeer", &args, &AuthRPCReply{}) + err = rclient.Call("BrowserPeer.SetAuthPeer", &args, &AuthRPCReply{}) if err != nil { if err.Error() != errInvalidToken.Error() { t.Fatal(err) diff --git a/cmd/browser-rpc-router.go b/cmd/browser-rpc-router.go index f7b762441..d88c5779d 100644 --- a/cmd/browser-rpc-router.go +++ b/cmd/browser-rpc-router.go @@ -45,7 +45,7 @@ func registerBrowserPeerRPCRouter(mux *router.Router) error { return traceError(err) } - bpRouter := mux.NewRoute().PathPrefix(reservedBucket).Subrouter() + bpRouter := mux.NewRoute().PathPrefix(minioReservedBucketPath).Subrouter() bpRouter.Path(browserPeerPath).Handler(bpRPCServer) return nil } diff --git a/cmd/bucket-handlers.go b/cmd/bucket-handlers.go index 96a6971c0..eda75d76e 100644 --- a/cmd/bucket-handlers.go +++ b/cmd/bucket-handlers.go @@ -19,7 +19,6 @@ package cmd import ( "encoding/base64" "encoding/xml" - "fmt" "io" "net/http" "net/url" @@ -50,6 +49,10 @@ func enforceBucketPolicy(bucket, action, resource, referer string, queryParams u return ErrInternalError } + if globalBucketPolicies == nil { + return ErrAccessDenied + } + // Fetch bucket policy, if policy is not set return access denied. policy := globalBucketPolicies.GetBucketPolicy(bucket) if policy == nil { @@ -86,10 +89,7 @@ func isBucketActionAllowed(action, bucket, prefix string) bool { resource := bucketARNPrefix + path.Join(bucket, prefix) var conditionKeyMap map[string]set.StringSet // Validate action, resource and conditions with current policy statements. - if !bucketPolicyEvalStatements(action, resource, conditionKeyMap, policy.Statements) { - return false - } - return true + return bucketPolicyEvalStatements(action, resource, conditionKeyMap, policy.Statements) } // GetBucketLocationHandler - GET Bucket location. @@ -277,7 +277,11 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter, for index, object := range deleteObjects.Objects { wg.Add(1) go func(i int, obj ObjectIdentifier) { + objectLock := globalNSMutex.NewNSLock(bucket, obj.ObjectName) + objectLock.Lock() + defer objectLock.Unlock() defer wg.Done() + dErr := objectAPI.DeleteObject(bucket, obj.ObjectName) if dErr != nil { dErrs[i] = dErr @@ -326,9 +330,7 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter, ObjInfo: ObjectInfo{ Name: dobj.ObjectName, }, - ReqParams: map[string]string{ - "sourceIPAddress": r.RemoteAddr, - }, + ReqParams: extractReqParams(r), }) } } @@ -438,14 +440,25 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h defer fileBody.Close() bucket := mux.Vars(r)["bucket"] - formValues["Bucket"] = bucket + formValues.Set("Bucket", bucket) - if fileName != "" && strings.Contains(formValues["Key"], "${filename}") { + if fileName != "" && strings.Contains(formValues.Get("Key"), "${filename}") { // S3 feature to replace ${filename} found in Key form field // by the filename attribute passed in multipart - formValues["Key"] = strings.Replace(formValues["Key"], "${filename}", fileName, -1) + formValues.Set("Key", strings.Replace(formValues.Get("Key"), "${filename}", fileName, -1)) + } + object := formValues.Get("Key") + + successRedirect := formValues.Get("success_action_redirect") + successStatus := formValues.Get("success_action_status") + var redirectURL *url.URL + if successRedirect != "" { + redirectURL, err = url.Parse(successRedirect) + if err != nil { + writeErrorResponse(w, ErrMalformedPOSTRequest, r.URL) + return + } } - object := formValues["Key"] // Verify policy signature. apiErr := doesPolicySignatureMatch(formValues) @@ -454,7 +467,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h return } - policyBytes, err := base64.StdEncoding.DecodeString(formValues["Policy"]) + policyBytes, err := base64.StdEncoding.DecodeString(formValues.Get("Policy")) if err != nil { writeErrorResponse(w, ErrMalformedPOSTRequest, r.URL) return @@ -482,7 +495,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h return } - if fileSize > lengthRange.Max || fileSize > maxObjectSize { + if fileSize > lengthRange.Max || isMaxObjectSize(fileSize) { errorIf(err, "Unable to create object.") writeErrorResponse(w, toAPIErrorCode(errDataTooLarge), r.URL) return @@ -491,7 +504,6 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h // Extract metadata to be saved from received Form. metadata := extractMetadataFromForm(formValues) - sha256sum := "" objectLock := globalNSMutex.NewNSLock(bucket, object) @@ -504,50 +516,40 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h writeErrorResponse(w, toAPIErrorCode(err), r.URL) return } - w.Header().Set("ETag", "\""+objInfo.MD5Sum+"\"") + + w.Header().Set("ETag", `"`+objInfo.MD5Sum+`"`) w.Header().Set("Location", getObjectLocation(bucket, object)) - successRedirect := formValues[http.CanonicalHeaderKey("success_action_redirect")] - successStatus := formValues[http.CanonicalHeaderKey("success_action_status")] + // Notify object created event. + defer eventNotify(eventData{ + Type: ObjectCreatedPost, + Bucket: objInfo.Bucket, + ObjInfo: objInfo, + ReqParams: extractReqParams(r), + }) - if successStatus == "" && successRedirect == "" { - writeSuccessNoContent(w) - } else { - if successRedirect != "" { - redirectURL := successRedirect + "?" + fmt.Sprintf("bucket=%s&key=%s&etag=%s", - bucket, - getURLEncodedName(object), - getURLEncodedName("\""+objInfo.MD5Sum+"\"")) - - writeRedirectSeeOther(w, redirectURL) - } else { - // Decide what http response to send depending on success_action_status parameter - switch successStatus { - case "201": - resp := encodeResponse(PostResponse{ - Bucket: bucket, - Key: object, - ETag: "\"" + objInfo.MD5Sum + "\"", - Location: getObjectLocation(bucket, object), - }) - writeResponse(w, http.StatusCreated, resp, "application/xml") - case "200": - writeSuccessResponseHeadersOnly(w) - default: - writeSuccessNoContent(w) - } - } + if successRedirect != "" { + // Replace raw query params.. + redirectURL.RawQuery = getRedirectPostRawQuery(objInfo) + writeRedirectSeeOther(w, redirectURL.String()) + return } - // Notify object created event. - eventNotify(eventData{ - Type: ObjectCreatedPost, - Bucket: bucket, - ObjInfo: objInfo, - ReqParams: map[string]string{ - "sourceIPAddress": r.RemoteAddr, - }, - }) + // Decide what http response to send depending on success_action_status parameter + switch successStatus { + case "201": + resp := encodeResponse(PostResponse{ + Bucket: objInfo.Bucket, + Key: objInfo.Name, + ETag: `"` + objInfo.MD5Sum + `"`, + Location: getObjectLocation(objInfo.Bucket, objInfo.Name), + }) + writeResponse(w, http.StatusCreated, resp, "application/xml") + case "200": + writeSuccessResponseHeadersOnly(w) + default: + writeSuccessNoContent(w) + } } // HeadBucketHandler - HEAD Bucket diff --git a/cmd/bucket-notification-handlers.go b/cmd/bucket-notification-handlers.go index ad4a6333c..96c68cf97 100644 --- a/cmd/bucket-notification-handlers.go +++ b/cmd/bucket-notification-handlers.go @@ -209,10 +209,7 @@ func writeNotification(w http.ResponseWriter, notification map[string][]Notifica _, err = w.Write(append(notificationBytes, crlf...)) // Make sure we have flushed, this would set Transfer-Encoding: chunked. w.(http.Flusher).Flush() - if err != nil { - return err - } - return nil + return err } // CRLF character used for chunked transfer in accordance with HTTP standards. diff --git a/cmd/bucket-notification-utils.go b/cmd/bucket-notification-utils.go index 8845c5cdb..39a719f12 100644 --- a/cmd/bucket-notification-utils.go +++ b/cmd/bucket-notification-utils.go @@ -253,19 +253,19 @@ func unmarshalSqsARN(queueARN string) (mSqs arnSQS) { } sqsType := strings.TrimPrefix(queueARN, minioSqs+serverConfig.GetRegion()+":") switch { - case strings.HasSuffix(sqsType, queueTypeAMQP): + case hasSuffix(sqsType, queueTypeAMQP): mSqs.Type = queueTypeAMQP - case strings.HasSuffix(sqsType, queueTypeNATS): + case hasSuffix(sqsType, queueTypeNATS): mSqs.Type = queueTypeNATS - case strings.HasSuffix(sqsType, queueTypeElastic): + case hasSuffix(sqsType, queueTypeElastic): mSqs.Type = queueTypeElastic - case strings.HasSuffix(sqsType, queueTypeRedis): + case hasSuffix(sqsType, queueTypeRedis): mSqs.Type = queueTypeRedis - case strings.HasSuffix(sqsType, queueTypePostgreSQL): + case hasSuffix(sqsType, queueTypePostgreSQL): mSqs.Type = queueTypePostgreSQL - case strings.HasSuffix(sqsType, queueTypeKafka): + case hasSuffix(sqsType, queueTypeKafka): mSqs.Type = queueTypeKafka - case strings.HasSuffix(sqsType, queueTypeWebhook): + case hasSuffix(sqsType, queueTypeWebhook): mSqs.Type = queueTypeWebhook } // Add more queues here. mSqs.AccountID = strings.TrimSuffix(sqsType, ":"+mSqs.Type) diff --git a/cmd/certs.go b/cmd/certs.go index ce24bf37f..af2696de8 100644 --- a/cmd/certs.go +++ b/cmd/certs.go @@ -1,5 +1,5 @@ /* - * Minio Cloud Storage, (C) 2015, 2016 Minio, Inc. + * Minio Cloud Storage, (C) 2015, 2016, 2017 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,148 +19,90 @@ package cmd import ( "crypto/x509" "encoding/pem" - "errors" + "fmt" "io/ioutil" - "os" "path/filepath" ) -// createCertsPath create certs path. -func createCertsPath() error { - certsPath, err := getCertsPath() - if err != nil { - return err - } - if err := os.MkdirAll(certsPath, 0700); err != nil { - return err - } - rootCAsPath := filepath.Join(certsPath, globalMinioCertsCADir) - return os.MkdirAll(rootCAsPath, 0700) -} - -// getCertsPath get certs path. -func getCertsPath() (string, error) { - var certsPath string - configDir, err := getConfigPath() - if err != nil { - return "", err - } - certsPath = filepath.Join(configDir, globalMinioCertsDir) - return certsPath, nil -} - -// mustGetCertsPath must get certs path. -func mustGetCertsPath() string { - certsPath, err := getCertsPath() - fatalIf(err, "Failed to get certificate path.") - return certsPath -} - -// mustGetCertFile must get cert file. -func mustGetCertFile() string { - return filepath.Join(mustGetCertsPath(), globalMinioCertFile) -} - -// mustGetKeyFile must get key file. -func mustGetKeyFile() string { - return filepath.Join(mustGetCertsPath(), globalMinioKeyFile) -} - -// mustGetCAFiles must get the list of the CA certificates stored in minio config dir -func mustGetCAFiles() (caCerts []string) { - CAsDir := filepath.Join(mustGetCertsPath(), globalMinioCertsCADir) - caFiles, _ := ioutil.ReadDir(CAsDir) - for _, cert := range caFiles { - caCerts = append(caCerts, filepath.Join(CAsDir, cert.Name())) - } - return -} - -// mustGetSystemCertPool returns empty cert pool in case of error (windows) -func mustGetSystemCertPool() *x509.CertPool { - pool, err := x509.SystemCertPool() - if err != nil { - return x509.NewCertPool() - } - return pool -} - -// isCertFileExists verifies if cert file exists, returns true if -// found, false otherwise. -func isCertFileExists() bool { - st, e := os.Stat(filepath.Join(mustGetCertsPath(), globalMinioCertFile)) - // If file exists and is regular return true. - if e == nil && st.Mode().IsRegular() { - return true - } - return false -} - -// isKeyFileExists verifies if key file exists, returns true if found, -// false otherwise. -func isKeyFileExists() bool { - st, e := os.Stat(filepath.Join(mustGetCertsPath(), globalMinioKeyFile)) - // If file exists and is regular return true. - if e == nil && st.Mode().IsRegular() { - return true - } - return false -} - // isSSL - returns true with both cert and key exists. func isSSL() bool { - return isCertFileExists() && isKeyFileExists() + return isFile(getPublicCertFile()) && isFile(getPrivateKeyFile()) } -// Reads certificated file and returns a list of parsed certificates. +func parsePublicCertFile(certFile string) (certs []*x509.Certificate, err error) { + var bytes []byte + + if bytes, err = ioutil.ReadFile(certFile); err != nil { + return certs, err + } + + // Parse all certs in the chain. + var block *pem.Block + var cert *x509.Certificate + current := bytes + for len(current) > 0 { + if block, current = pem.Decode(current); block == nil { + err = fmt.Errorf("Could not read PEM block from file %s", certFile) + return certs, err + } + + if cert, err = x509.ParseCertificate(block.Bytes); err != nil { + return certs, err + } + + certs = append(certs, cert) + } + + if len(certs) == 0 { + err = fmt.Errorf("Empty public certificate file %s", certFile) + } + + return certs, err +} + +// Reads certificate file and returns a list of parsed certificates. func readCertificateChain() ([]*x509.Certificate, error) { - bytes, err := ioutil.ReadFile(mustGetCertFile()) + return parsePublicCertFile(getPublicCertFile()) +} + +func getRootCAs(certsCAsDir string) (*x509.CertPool, error) { + // Get all CA file names. + var caFiles []string + fis, err := ioutil.ReadDir(certsCAsDir) if err != nil { return nil, err } - - // Proceed to parse the certificates. - return parseCertificateChain(bytes) -} - -// Parses certificate chain, returns a list of parsed certificates. -func parseCertificateChain(bytes []byte) ([]*x509.Certificate, error) { - var certs []*x509.Certificate - var block *pem.Block - current := bytes - - // Parse all certs in the chain. - for len(current) > 0 { - block, current = pem.Decode(current) - if block == nil { - return nil, errors.New("Could not PEM block") - } - // Parse the decoded certificate. - cert, err := x509.ParseCertificate(block.Bytes) - if err != nil { - return nil, err - } - certs = append(certs, cert) - + for _, fi := range fis { + caFiles = append(caFiles, filepath.Join(certsCAsDir, fi.Name())) } - return certs, nil -} -// loadRootCAs fetches CA files provided in minio config and adds them to globalRootCAs -// Currently under Windows, there is no way to load system + user CAs at the same time -func loadRootCAs() { - caFiles := mustGetCAFiles() if len(caFiles) == 0 { - return + return nil, nil } - // Get system cert pool, and empty cert pool under Windows because it is not supported - globalRootCAs = mustGetSystemCertPool() + + rootCAs, err := x509.SystemCertPool() + if err != nil { + // In some systems like Windows, system cert pool is not supported. + // Hence we create a new cert pool. + rootCAs = x509.NewCertPool() + } + // Load custom root CAs for client requests for _, caFile := range caFiles { caCert, err := ioutil.ReadFile(caFile) if err != nil { - fatalIf(err, "Unable to load a CA file") + return rootCAs, err } - globalRootCAs.AppendCertsFromPEM(caCert) + + rootCAs.AppendCertsFromPEM(caCert) } + + return rootCAs, nil +} + +// loadRootCAs fetches CA files provided in minio config and adds them to globalRootCAs +// Currently under Windows, there is no way to load system + user CAs at the same time +func loadRootCAs() (err error) { + globalRootCAs, err = getRootCAs(getCADir()) + return err } diff --git a/cmd/certs_test.go b/cmd/certs_test.go index a68f8176e..730f311ff 100644 --- a/cmd/certs_test.go +++ b/cmd/certs_test.go @@ -1,5 +1,5 @@ /* - * Minio Cloud Storage, (C) 2015, 2016 Minio, Inc. + * Minio Cloud Storage, (C) 2015, 2016, 2017 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,52 +17,86 @@ package cmd import ( + "fmt" + "io/ioutil" "os" "path/filepath" - "strings" + "runtime" "testing" ) -// Make sure we have a valid certs path. -func TestGetCertsPath(t *testing.T) { - path, err := getCertsPath() +func createTempFile(prefix, content string) (tempFile string, err error) { + var tmpfile *os.File + + if tmpfile, err = ioutil.TempFile("", prefix); err != nil { + return tempFile, err + } + + if _, err = tmpfile.Write([]byte(content)); err != nil { + return tempFile, err + } + + if err = tmpfile.Close(); err != nil { + return tempFile, err + } + + tempFile = tmpfile.Name() + return tempFile, err +} + +func TestParsePublicCertFile(t *testing.T) { + tempFile1, err := createTempFile("public-cert-file", "") if err != nil { - t.Error(err) - } - if path == "" { - t.Errorf("expected path to not be an empty string, got: '%s'", path) - } - // Ensure it contains some sort of path separator. - if !strings.ContainsRune(path, os.PathSeparator) { - t.Errorf("expected path to contain file separator") - } - // It should also be an absolute path. - if !filepath.IsAbs(path) { - t.Errorf("expected path to be an absolute path") + t.Fatalf("Unable to create temporary file. %v", err) } + defer os.Remove(tempFile1) - // This will error if something goes wrong, so just call it. - mustGetCertsPath() -} - -// Ensure that the certificate and key file getters contain their respective -// file name and endings. -func TestGetFiles(t *testing.T) { - file := mustGetCertFile() - if !strings.Contains(file, globalMinioCertFile) { - t.Errorf("CertFile does not contain %s", globalMinioCertFile) + tempFile2, err := createTempFile("public-cert-file", + `-----BEGIN CERTIFICATE----- +MIICdTCCAd4CCQCO5G/W1xcE9TANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJa +WTEOMAwGA1UECBMFTWluaW8xETAPBgNVBAcTCEludGVybmV0MQ4wDAYDVQQKEwVN +aW5pbzEOMAwGA1UECxMFTWluaW8xDjAMBgNVBAMTBU1pbmlvMR0wGwYJKoZIhvcN +AQkBFg50ZXN0c0BtaW5pby5pbzAeFw0xNjEwMTQxMTM0MjJaFw0xNzEwMTQxMTM0 +MjJaMH8xCzAJBgNVBAYTAlpZMQ4wDAYDVQQIEwVNaW5pbzERMA8GA1UEBxMISW50 +ZXJuZXQxDjAMBgNVBA-some-junk-Q4wDAYDVQQLEwVNaW5pbzEOMAwGA1UEAxMF +TWluaW8xHTAbBgkqhkiG9w0BCQEWDnRlc3RzQG1pbmlvLmlvMIGfMA0GCSqGSIb3 +DQEBAQUAA4GNADCBiQKBgQDwNUYB/Sj79WsUE8qnXzzh2glSzWxUE79sCOpQYK83 +HWkrl5WxlG8ZxDR1IQV9Ex/lzigJu8G+KXahon6a+3n5GhNrYRe5kIXHQHz0qvv4 +aMulqlnYpvSfC83aaO9GVBtwXS/O4Nykd7QBg4nZlazVmsGk7POOjhpjGShRsqpU +JwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBALqjOA6bD8BEl7hkQ8XwX/owSAL0URDe +nUfCOsXgIIAqgw4uTCLOfCJVZNKmRT+KguvPAQ6Z80vau2UxPX5Q2Q+OHXDRrEnK +FjqSBgLP06Qw7a++bshlWGTt5bHWOneW3EQikedckVuIKPkOCib9yGi4VmBBjdFE +M9ofSEt/bdRD +-----END CERTIFICATE-----`) + if err != nil { + t.Fatalf("Unable to create temporary file. %v", err) } + defer os.Remove(tempFile2) - file = mustGetKeyFile() - if !strings.Contains(file, globalMinioKeyFile) { - t.Errorf("KeyFile does not contain %s", globalMinioKeyFile) + tempFile3, err := createTempFile("public-cert-file", + `-----BEGIN CERTIFICATE----- +MIICdTCCAd4CCQCO5G/W1xcE9TANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJa +WTEOMAwGA1UECBMFTWluaW8xETAPBgNVBAcTCEludGVybmV0MQ4wDAYDVQQKEwVN +aW5pbzEOMAwGA1UECxMFTWluaW8xDjAMBgNVBAMTBU1pbmlvMR0wGwYJKoZIhvcN +AQkBFg50ZXN0c0BtaW5pby5pbzAeFw0xNjEwMTQxMTM0MjJaFw0xNzEwMTQxMTM0 +MjJaMH8xCzAJBgNVBAYTAlpZMQ4wDAYDVQQIEwVNaW5pbzERMA8GA1UEBxMISW50 +ZXJuZXQxDjAMBgNVBAabababababaQ4wDAYDVQQLEwVNaW5pbzEOMAwGA1UEAxMF +TWluaW8xHTAbBgkqhkiG9w0BCQEWDnRlc3RzQG1pbmlvLmlvMIGfMA0GCSqGSIb3 +DQEBAQUAA4GNADCBiQKBgQDwNUYB/Sj79WsUE8qnXzzh2glSzWxUE79sCOpQYK83 +HWkrl5WxlG8ZxDR1IQV9Ex/lzigJu8G+KXahon6a+3n5GhNrYRe5kIXHQHz0qvv4 +aMulqlnYpvSfC83aaO9GVBtwXS/O4Nykd7QBg4nZlazVmsGk7POOjhpjGShRsqpU +JwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBALqjOA6bD8BEl7hkQ8XwX/owSAL0URDe +nUfCOsXgIIAqgw4uTCLOfCJVZNKmRT+KguvPAQ6Z80vau2UxPX5Q2Q+OHXDRrEnK +FjqSBgLP06Qw7a++bshlWGTt5bHWOneW3EQikedckVuIKPkOCib9yGi4VmBBjdFE +M9ofSEt/bdRD +-----END CERTIFICATE-----`) + if err != nil { + t.Fatalf("Unable to create temporary file. %v", err) } -} + defer os.Remove(tempFile3) -// Parses .crt file contents -func TestParseCertificateChain(t *testing.T) { - // given - cert := `-----BEGIN CERTIFICATE----- + tempFile4, err := createTempFile("public-cert-file", + `-----BEGIN CERTIFICATE----- MIICdTCCAd4CCQCO5G/W1xcE9TANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJa WTEOMAwGA1UECBMFTWluaW8xETAPBgNVBAcTCEludGVybmV0MQ4wDAYDVQQKEwVN aW5pbzEOMAwGA1UECxMFTWluaW8xDjAMBgNVBAMTBU1pbmlvMR0wGwYJKoZIhvcN @@ -77,35 +111,149 @@ JwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBALqjOA6bD8BEl7hkQ8XwX/owSAL0URDe nUfCOsXgIIAqgw4uTCLOfCJVZNKmRT+KguvPAQ6Z80vau2UxPX5Q2Q+OHXDRrEnK FjqSBgLP06Qw7a++bshlWGTt5bHWOneW3EQikedckVuIKPkOCib9yGi4VmBBjdFE M9ofSEt/bdRD ------END CERTIFICATE-----` - - // when - certs, err := parseCertificateChain([]byte(cert)) - - // then +-----END CERTIFICATE-----`) if err != nil { - t.Fatalf("Could not parse certificate: %s", err) + t.Fatalf("Unable to create temporary file. %v", err) + } + defer os.Remove(tempFile4) + + tempFile5, err := createTempFile("public-cert-file", + `-----BEGIN CERTIFICATE----- +MIICdTCCAd4CCQCO5G/W1xcE9TANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJa +WTEOMAwGA1UECBMFTWluaW8xETAPBgNVBAcTCEludGVybmV0MQ4wDAYDVQQKEwVN +aW5pbzEOMAwGA1UECxMFTWluaW8xDjAMBgNVBAMTBU1pbmlvMR0wGwYJKoZIhvcN +AQkBFg50ZXN0c0BtaW5pby5pbzAeFw0xNjEwMTQxMTM0MjJaFw0xNzEwMTQxMTM0 +MjJaMH8xCzAJBgNVBAYTAlpZMQ4wDAYDVQQIEwVNaW5pbzERMA8GA1UEBxMISW50 +ZXJuZXQxDjAMBgNVBAoTBU1pbmlvMQ4wDAYDVQQLEwVNaW5pbzEOMAwGA1UEAxMF +TWluaW8xHTAbBgkqhkiG9w0BCQEWDnRlc3RzQG1pbmlvLmlvMIGfMA0GCSqGSIb3 +DQEBAQUAA4GNADCBiQKBgQDwNUYB/Sj79WsUE8qnXzzh2glSzWxUE79sCOpQYK83 +HWkrl5WxlG8ZxDR1IQV9Ex/lzigJu8G+KXahon6a+3n5GhNrYRe5kIXHQHz0qvv4 +aMulqlnYpvSfC83aaO9GVBtwXS/O4Nykd7QBg4nZlazVmsGk7POOjhpjGShRsqpU +JwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBALqjOA6bD8BEl7hkQ8XwX/owSAL0URDe +nUfCOsXgIIAqgw4uTCLOfCJVZNKmRT+KguvPAQ6Z80vau2UxPX5Q2Q+OHXDRrEnK +FjqSBgLP06Qw7a++bshlWGTt5bHWOneW3EQikedckVuIKPkOCib9yGi4VmBBjdFE +M9ofSEt/bdRD +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICdTCCAd4CCQCO5G/W1xcE9TANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJa +WTEOMAwGA1UECBMFTWluaW8xETAPBgNVBAcTCEludGVybmV0MQ4wDAYDVQQKEwVN +aW5pbzEOMAwGA1UECxMFTWluaW8xDjAMBgNVBAMTBU1pbmlvMR0wGwYJKoZIhvcN +AQkBFg50ZXN0c0BtaW5pby5pbzAeFw0xNjEwMTQxMTM0MjJaFw0xNzEwMTQxMTM0 +MjJaMH8xCzAJBgNVBAYTAlpZMQ4wDAYDVQQIEwVNaW5pbzERMA8GA1UEBxMISW50 +ZXJuZXQxDjAMBgNVBAoTBU1pbmlvMQ4wDAYDVQQLEwVNaW5pbzEOMAwGA1UEAxMF +TWluaW8xHTAbBgkqhkiG9w0BCQEWDnRlc3RzQG1pbmlvLmlvMIGfMA0GCSqGSIb3 +DQEBAQUAA4GNADCBiQKBgQDwNUYB/Sj79WsUE8qnXzzh2glSzWxUE79sCOpQYK83 +HWkrl5WxlG8ZxDR1IQV9Ex/lzigJu8G+KXahon6a+3n5GhNrYRe5kIXHQHz0qvv4 +aMulqlnYpvSfC83aaO9GVBtwXS/O4Nykd7QBg4nZlazVmsGk7POOjhpjGShRsqpU +JwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBALqjOA6bD8BEl7hkQ8XwX/owSAL0URDe +nUfCOsXgIIAqgw4uTCLOfCJVZNKmRT+KguvPAQ6Z80vau2UxPX5Q2Q+OHXDRrEnK +FjqSBgLP06Qw7a++bshlWGTt5bHWOneW3EQikedckVuIKPkOCib9yGi4VmBBjdFE +M9ofSEt/bdRD +-----END CERTIFICATE-----`) + if err != nil { + t.Fatalf("Unable to create temporary file. %v", err) + } + defer os.Remove(tempFile5) + + nonexistentErr := fmt.Errorf("open nonexistent-file: no such file or directory") + if runtime.GOOS == "windows" { + // Below concatenation is done to get rid of goline error + // "error strings should not be capitalized or end with punctuation or a newline" + nonexistentErr = fmt.Errorf("open nonexistent-file:" + " The system cannot find the file specified.") } - if len(certs) != 1 { - t.Fatalf("Expected number of certificates in chain was 1, actual: %d", len(certs)) + testCases := []struct { + certFile string + expectedResultLen int + expectedErr error + }{ + {"nonexistent-file", 0, nonexistentErr}, + {tempFile1, 0, fmt.Errorf("Empty public certificate file %s", tempFile1)}, + {tempFile2, 0, fmt.Errorf("Could not read PEM block from file %s", tempFile2)}, + {tempFile3, 0, fmt.Errorf("asn1: structure error: sequence tag mismatch")}, + {tempFile4, 1, nil}, + {tempFile5, 2, nil}, } - if certs[0].Subject.CommonName != "Minio" { - t.Fatalf("Expected Subject.CommonName was Minio, actual: %s", certs[0].Subject.CommonName) + for _, testCase := range testCases { + certs, err := parsePublicCertFile(testCase.certFile) + + if testCase.expectedErr == nil { + if err != nil { + t.Fatalf("error: expected = , got = %v", err) + } + } else if err == nil { + t.Fatalf("error: expected = %v, got = ", testCase.expectedErr) + } else if testCase.expectedErr.Error() != err.Error() { + t.Fatalf("error: expected = %v, got = %v", testCase.expectedErr, err) + } + + if len(certs) != testCase.expectedResultLen { + t.Fatalf("certs: expected = %v, got = %v", testCase.expectedResultLen, len(certs)) + } } } -// Parses invalid .crt file contents and returns error -func TestParseInvalidCertificateChain(t *testing.T) { - // given - cert := `This is now valid certificate` +func TestGetRootCAs(t *testing.T) { + emptydir, err := ioutil.TempDir("", "test-get-root-cas") + if err != nil { + t.Fatalf("Unable create temp directory. %v", emptydir) + } + defer os.RemoveAll(emptydir) - // when - _, err := parseCertificateChain([]byte(cert)) + dir1, err := ioutil.TempDir("", "test-get-root-cas") + if err != nil { + t.Fatalf("Unable create temp directory. %v", dir1) + } + defer os.RemoveAll(dir1) + if err = os.Mkdir(filepath.Join(dir1, "empty-dir"), 0755); err != nil { + t.Fatalf("Unable create empty dir. %v", err) + } - // then - if err == nil { - t.Fatalf("Expected error but none occurred") + dir2, err := ioutil.TempDir("", "test-get-root-cas") + if err != nil { + t.Fatalf("Unable create temp directory. %v", dir2) + } + defer os.RemoveAll(dir2) + if err = ioutil.WriteFile(filepath.Join(dir2, "empty-file"), []byte{}, 0644); err != nil { + t.Fatalf("Unable create test file. %v", err) + } + + nonexistentErr := fmt.Errorf("open nonexistent-dir: no such file or directory") + if runtime.GOOS == "windows" { + // Below concatenation is done to get rid of goline error + // "error strings should not be capitalized or end with punctuation or a newline" + nonexistentErr = fmt.Errorf("open nonexistent-dir:" + " The system cannot find the file specified.") + } + + err1 := fmt.Errorf("read %s: is a directory", filepath.Join(dir1, "empty-dir")) + if runtime.GOOS == "windows" { + // Below concatenation is done to get rid of goline error + // "error strings should not be capitalized or end with punctuation or a newline" + err1 = fmt.Errorf("read %s:"+" The handle is invalid.", filepath.Join(dir1, "empty-dir")) + } + + testCases := []struct { + certCAsDir string + expectedErr error + }{ + {"nonexistent-dir", nonexistentErr}, + {dir1, err1}, + {emptydir, nil}, + {dir2, nil}, + } + + for _, testCase := range testCases { + _, err := getRootCAs(testCase.certCAsDir) + + if testCase.expectedErr == nil { + if err != nil { + t.Fatalf("error: expected = , got = %v", err) + } + } else if err == nil { + t.Fatalf("error: expected = %v, got = ", testCase.expectedErr) + } else if testCase.expectedErr.Error() != err.Error() { + t.Fatalf("error: expected = %v, got = %v", testCase.expectedErr, err) + } } } diff --git a/cmd/config-dir.go b/cmd/config-dir.go new file mode 100644 index 000000000..fc46a8b33 --- /dev/null +++ b/cmd/config-dir.go @@ -0,0 +1,139 @@ +/* + * Minio Cloud Storage, (C) 2015, 2016, 2017 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cmd + +import ( + "path/filepath" + "sync" + + homedir "github.com/minio/go-homedir" + "github.com/minio/mc/pkg/console" +) + +const ( + // Default minio configuration directory where below configuration files/directories are stored. + defaultMinioConfigDir = ".minio" + + // Minio configuration file. + minioConfigFile = "config.json" + + // Directory contains below files/directories for HTTPS configuration. + certsDir = "certs" + + // Directory contains all CA certificates other than system defaults for HTTPS. + certsCADir = "CAs" + + // Public certificate file for HTTPS. + publicCertFile = "public.crt" + + // Private key file for HTTPS. + privateKeyFile = "private.key" +) + +// ConfigDir - configuration directory with locking. +type ConfigDir struct { + sync.Mutex + dir string +} + +// Set - saves given directory as configuration directory. +func (config *ConfigDir) Set(dir string) { + config.Lock() + defer config.Unlock() + + config.dir = dir +} + +// Get - returns current configuration directory. +func (config *ConfigDir) Get() string { + config.Lock() + defer config.Unlock() + + return config.dir +} + +func (config *ConfigDir) getCertsDir() string { + return filepath.Join(config.Get(), certsDir) +} + +// GetCADir - returns certificate CA directory. +func (config *ConfigDir) GetCADir() string { + return filepath.Join(config.getCertsDir(), certsCADir) +} + +// Create - creates configuration directory tree. +func (config *ConfigDir) Create() error { + return mkdirAll(config.GetCADir(), 0700) +} + +// GetMinioConfigFile - returns absolute path of config.json file. +func (config *ConfigDir) GetMinioConfigFile() string { + return filepath.Join(config.Get(), minioConfigFile) +} + +// GetPublicCertFile - returns absolute path of public.crt file. +func (config *ConfigDir) GetPublicCertFile() string { + return filepath.Join(config.getCertsDir(), publicCertFile) +} + +// GetPrivateKeyFile - returns absolute path of private.key file. +func (config *ConfigDir) GetPrivateKeyFile() string { + return filepath.Join(config.getCertsDir(), privateKeyFile) +} + +func mustGetDefaultConfigDir() string { + homeDir, err := homedir.Dir() + if err != nil { + console.Fatalln("Unable to get home directory.", err) + } + + return filepath.Join(homeDir, defaultMinioConfigDir) +} + +var configDir = &ConfigDir{dir: mustGetDefaultConfigDir()} + +func setConfigDir(dir string) { + configDir.Set(dir) +} + +func getConfigDir() string { + return configDir.Get() +} + +func getCADir() string { + return configDir.GetCADir() +} + +func createConfigDir() error { + return configDir.Create() +} + +func getConfigFile() string { + return configDir.GetMinioConfigFile() +} + +func getPublicCertFile() string { + return configDir.GetPublicCertFile() +} + +func getPrivateKeyFile() string { + return configDir.GetPrivateKeyFile() +} + +func isConfigFileExists() bool { + return isFile(getConfigFile()) +} diff --git a/cmd/config-migrate.go b/cmd/config-migrate.go index d6a07b045..f75b2c18e 100644 --- a/cmd/config-migrate.go +++ b/cmd/config-migrate.go @@ -74,6 +74,10 @@ func migrateConfig() error { if err := migrateV12ToV13(); err != nil { return err } + // Migration version '13' to '14'. + if err := migrateV13ToV14(); err != nil { + return err + } return nil } @@ -90,15 +94,10 @@ func purgeV1() error { } if cv1.Version == "1" { - console.Println("Removed unsupported config version ‘1’.") - /// Purge old fsUsers.json file - configPath, err := getConfigPath() - if err != nil { - return fmt.Errorf("Unable to retrieve config path. %v", err) - } - - configFile := filepath.Join(configPath, "fsUsers.json") + // Purge old fsUsers.json file + configFile := filepath.Join(getConfigDir(), "fsUsers.json") removeAll(configFile) + console.Println("Removed unsupported config version ‘1’.") return nil } return fmt.Errorf("Failed to migrate unrecognized config version ‘" + cv1.Version + "’.") @@ -117,13 +116,16 @@ func migrateV2ToV3() error { if cv2.Version != "2" { return nil } + + cred, err := createCredential(cv2.Credentials.AccessKey, cv2.Credentials.SecretKey) + if err != nil { + return fmt.Errorf("Invalid credential in V2 configuration file. %v", err) + } + srvConfig := &configV3{} srvConfig.Version = "3" srvConfig.Addr = ":9000" - srvConfig.Credential = credential{ - AccessKey: cv2.Credentials.AccessKey, - SecretKey: cv2.Credentials.SecretKey, - } + srvConfig.Credential = cred srvConfig.Region = cv2.Credentials.Region if srvConfig.Region == "" { // Region needs to be set for AWS Signature V4. @@ -154,12 +156,7 @@ func migrateV2ToV3() error { return fmt.Errorf("Unable to initialize config. %v", err) } - configFile, err := getConfigFile() - if err != nil { - return fmt.Errorf("Unable to get config file. %v", err) - } - - // Migrate the config. + configFile := getConfigFile() err = qc.Save(configFile) if err != nil { return fmt.Errorf("Failed to migrate config from ‘"+cv2.Version+"’ to ‘"+srvConfig.Version+"’ failed. %v", err) @@ -201,11 +198,8 @@ func migrateV3ToV4() error { if err != nil { return fmt.Errorf("Unable to initialize the quick config. %v", err) } - configFile, err := getConfigFile() - if err != nil { - return fmt.Errorf("Unable to get config file. %v", err) - } + configFile := getConfigFile() err = qc.Save(configFile) if err != nil { return fmt.Errorf("Failed to migrate config from ‘"+cv3.Version+"’ to ‘"+srvConfig.Version+"’ failed. %v", err) @@ -250,11 +244,8 @@ func migrateV4ToV5() error { if err != nil { return fmt.Errorf("Unable to initialize the quick config. %v", err) } - configFile, err := getConfigFile() - if err != nil { - return fmt.Errorf("Unable to get config file. %v", err) - } + configFile := getConfigFile() err = qc.Save(configFile) if err != nil { return fmt.Errorf("Failed to migrate config from ‘"+cv4.Version+"’ to ‘"+srvConfig.Version+"’ failed. %v", err) @@ -326,11 +317,8 @@ func migrateV5ToV6() error { if err != nil { return fmt.Errorf("Unable to initialize the quick config. %v", err) } - configFile, err := getConfigFile() - if err != nil { - return fmt.Errorf("Unable to get config file. %v", err) - } + configFile := getConfigFile() err = qc.Save(configFile) if err != nil { return fmt.Errorf("Failed to migrate config from ‘"+cv5.Version+"’ to ‘"+srvConfig.Version+"’ failed. %v", err) @@ -390,11 +378,8 @@ func migrateV6ToV7() error { if err != nil { return fmt.Errorf("Unable to initialize the quick config. %v", err) } - configFile, err := getConfigFile() - if err != nil { - return fmt.Errorf("Unable to get config file. %v", err) - } + configFile := getConfigFile() err = qc.Save(configFile) if err != nil { return fmt.Errorf("Failed to migrate config from ‘"+cv6.Version+"’ to ‘"+srvConfig.Version+"’ failed. %v", err) @@ -461,11 +446,8 @@ func migrateV7ToV8() error { if err != nil { return fmt.Errorf("Unable to initialize the quick config. %v", err) } - configFile, err := getConfigFile() - if err != nil { - return fmt.Errorf("Unable to get config file. %v", err) - } + configFile := getConfigFile() err = qc.Save(configFile) if err != nil { return fmt.Errorf("Failed to migrate config from ‘"+cv7.Version+"’ to ‘"+srvConfig.Version+"’ failed. %v", err) @@ -540,11 +522,8 @@ func migrateV8ToV9() error { return fmt.Errorf("Unable to initialize the quick config. %v", err) } - configFile, err := getConfigFile() - if err != nil { - return fmt.Errorf("Unable to get config file. %v", err) - } + configFile := getConfigFile() err = qc.Save(configFile) if err != nil { return fmt.Errorf( @@ -625,11 +604,8 @@ func migrateV9ToV10() error { return fmt.Errorf("Unable to initialize the quick config. %v", err) } - configFile, err := getConfigFile() - if err != nil { - return fmt.Errorf("Unable to get config file. %v", err) - } + configFile := getConfigFile() err = qc.Save(configFile) if err != nil { return fmt.Errorf( @@ -713,11 +689,8 @@ func migrateV10ToV11() error { return fmt.Errorf("Unable to initialize the quick config. %v", err) } - configFile, err := getConfigFile() - if err != nil { - return fmt.Errorf("Unable to get config file. %v", err) - } + configFile := getConfigFile() err = qc.Save(configFile) if err != nil { return fmt.Errorf( @@ -819,11 +792,8 @@ func migrateV11ToV12() error { return fmt.Errorf("Unable to initialize the quick config. %v", err) } - configFile, err := getConfigFile() - if err != nil { - return fmt.Errorf("Unable to get config file. %v", err) - } + configFile := getConfigFile() err = qc.Save(configFile) if err != nil { return fmt.Errorf( @@ -916,11 +886,8 @@ func migrateV12ToV13() error { return fmt.Errorf("Unable to initialize the quick config. %v", err) } - configFile, err := getConfigFile() - if err != nil { - return fmt.Errorf("Unable to get config file. %v", err) - } + configFile := getConfigFile() err = qc.Save(configFile) if err != nil { return fmt.Errorf( @@ -937,3 +904,102 @@ func migrateV12ToV13() error { ) return nil } + +// Version '13' to '14' migration. Add support for custom webhook endpoint. +func migrateV13ToV14() error { + cv13, err := loadConfigV13() + if err != nil { + if os.IsNotExist(err) { + return nil + } + return fmt.Errorf("Unable to load config version ‘13’. %v", err) + } + if cv13.Version != "13" { + return nil + } + + // Copy over fields from V13 into V14 config struct + srvConfig := &serverConfigV14{ + Logger: &logger{}, + Notify: ¬ifier{}, + } + srvConfig.Version = "14" + srvConfig.Credential = cv13.Credential + srvConfig.Region = cv13.Region + if srvConfig.Region == "" { + // Region needs to be set for AWS Signature Version 4. + srvConfig.Region = globalMinioDefaultRegion + } + srvConfig.Logger.Console = cv13.Logger.Console + srvConfig.Logger.File = cv13.Logger.File + + // check and set notifiers config + if len(cv13.Notify.AMQP) == 0 { + srvConfig.Notify.AMQP = make(map[string]amqpNotify) + srvConfig.Notify.AMQP["1"] = amqpNotify{} + } else { + srvConfig.Notify.AMQP = cv13.Notify.AMQP + } + if len(cv13.Notify.ElasticSearch) == 0 { + srvConfig.Notify.ElasticSearch = make(map[string]elasticSearchNotify) + srvConfig.Notify.ElasticSearch["1"] = elasticSearchNotify{} + } else { + srvConfig.Notify.ElasticSearch = cv13.Notify.ElasticSearch + } + if len(cv13.Notify.Redis) == 0 { + srvConfig.Notify.Redis = make(map[string]redisNotify) + srvConfig.Notify.Redis["1"] = redisNotify{} + } else { + srvConfig.Notify.Redis = cv13.Notify.Redis + } + if len(cv13.Notify.PostgreSQL) == 0 { + srvConfig.Notify.PostgreSQL = make(map[string]postgreSQLNotify) + srvConfig.Notify.PostgreSQL["1"] = postgreSQLNotify{} + } else { + srvConfig.Notify.PostgreSQL = cv13.Notify.PostgreSQL + } + if len(cv13.Notify.Kafka) == 0 { + srvConfig.Notify.Kafka = make(map[string]kafkaNotify) + srvConfig.Notify.Kafka["1"] = kafkaNotify{} + } else { + srvConfig.Notify.Kafka = cv13.Notify.Kafka + } + if len(cv13.Notify.NATS) == 0 { + srvConfig.Notify.NATS = make(map[string]natsNotify) + srvConfig.Notify.NATS["1"] = natsNotify{} + } else { + srvConfig.Notify.NATS = cv13.Notify.NATS + } + if len(cv13.Notify.Webhook) == 0 { + srvConfig.Notify.Webhook = make(map[string]webhookNotify) + srvConfig.Notify.Webhook["1"] = webhookNotify{} + } else { + srvConfig.Notify.Webhook = cv13.Notify.Webhook + } + + // Set the new browser parameter to true by default + srvConfig.Browser = "on" + + qc, err := quick.New(srvConfig) + if err != nil { + return fmt.Errorf("Unable to initialize the quick config. %v", + err) + } + + configFile := getConfigFile() + err = qc.Save(configFile) + if err != nil { + return fmt.Errorf( + "Failed to migrate config from ‘"+ + cv13.Version+"’ to ‘"+srvConfig.Version+ + "’ failed. %v", err, + ) + } + + console.Println( + "Migration from version ‘" + + cv13.Version + "’ to ‘" + srvConfig.Version + + "’ completed successfully.", + ) + return nil +} diff --git a/cmd/config-migrate_test.go b/cmd/config-migrate_test.go index b1fb6e24e..e30818dd5 100644 --- a/cmd/config-migrate_test.go +++ b/cmd/config-migrate_test.go @@ -31,7 +31,7 @@ func TestServerConfigMigrateV1(t *testing.T) { // remove the root directory after the test ends. defer removeAll(rootPath) - setGlobalConfigPath(rootPath) + setConfigDir(rootPath) // Create a V1 config json file and store it configJSON := "{ \"version\":\"1\", \"accessKeyId\":\"abcde\", \"secretAccessKey\":\"abcdefgh\"}" @@ -50,7 +50,7 @@ func TestServerConfigMigrateV1(t *testing.T) { } // Initialize server config and check again if everything is fine - if err := loadConfig(credential{}); err != nil { + if err := loadConfig(envParams{}); err != nil { t.Fatalf("Unable to initialize from updated config file %s", err) } } @@ -65,8 +65,8 @@ func TestServerConfigMigrateInexistentConfig(t *testing.T) { // remove the root directory after the test ends. defer removeAll(rootPath) - setGlobalConfigPath(rootPath) - configPath := rootPath + "/" + globalMinioConfigFile + setConfigDir(rootPath) + configPath := rootPath + "/" + minioConfigFile // Remove config file if err := os.Remove(configPath); err != nil { @@ -106,10 +106,13 @@ func TestServerConfigMigrateInexistentConfig(t *testing.T) { if err := migrateV12ToV13(); err != nil { t.Fatal("migrate v12 to v13 should succeed when no config file is found") } + if err := migrateV13ToV14(); err != nil { + t.Fatal("migrate v13 to v14 should succeed when no config file is found") + } } // Test if a config migration from v2 to v12 is successfully done -func TestServerConfigMigrateV2toV12(t *testing.T) { +func TestServerConfigMigrateV2toV14(t *testing.T) { rootPath, err := newTestConfig(globalMinioDefaultRegion) if err != nil { t.Fatalf("Init Test config failed") @@ -117,8 +120,8 @@ func TestServerConfigMigrateV2toV12(t *testing.T) { // remove the root directory after the test ends. defer removeAll(rootPath) - setGlobalConfigPath(rootPath) - configPath := rootPath + "/" + globalMinioConfigFile + setConfigDir(rootPath) + configPath := rootPath + "/" + minioConfigFile // Create a corrupted config file if err := ioutil.WriteFile(configPath, []byte("{ \"version\":\"2\","), 0644); err != nil { @@ -143,12 +146,12 @@ func TestServerConfigMigrateV2toV12(t *testing.T) { } // Initialize server config and check again if everything is fine - if err := loadConfig(credential{}); err != nil { + if err := loadConfig(envParams{}); err != nil { t.Fatalf("Unable to initialize from updated config file %s", err) } // Check the version number in the upgraded config file - expectedVersion := globalMinioConfigVersion + expectedVersion := v14 if serverConfig.Version != expectedVersion { t.Fatalf("Expect version "+expectedVersion+", found: %v", serverConfig.Version) } @@ -171,8 +174,8 @@ func TestServerConfigMigrateFaultyConfig(t *testing.T) { // remove the root directory after the test ends. defer removeAll(rootPath) - setGlobalConfigPath(rootPath) - configPath := rootPath + "/" + globalMinioConfigFile + setConfigDir(rootPath) + configPath := rootPath + "/" + minioConfigFile // Create a corrupted config file if err := ioutil.WriteFile(configPath, []byte("{ \"version\":\""), 0644); err != nil { @@ -213,4 +216,7 @@ func TestServerConfigMigrateFaultyConfig(t *testing.T) { if err := migrateV12ToV13(); err == nil { t.Fatal("migrateConfigV12ToV13() should fail with a corrupted json") } + if err := migrateV13ToV14(); err == nil { + t.Fatal("migrateConfigV13ToV14() should fail with a corrupted json") + } } diff --git a/cmd/config-old.go b/cmd/config-old.go index 2914dd597..7f65003e3 100644 --- a/cmd/config-old.go +++ b/cmd/config-old.go @@ -1,3 +1,19 @@ +/* + * Minio Cloud Storage, (C) 2016, 2017 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package cmd import ( @@ -8,6 +24,23 @@ import ( "github.com/minio/minio/pkg/quick" ) +func loadOldConfig(configFile string, config interface{}) (interface{}, error) { + if _, err := os.Stat(configFile); err != nil { + return nil, err + } + + qc, err := quick.New(config) + if err != nil { + return nil, err + } + + if err = qc.Load(configFile); err != nil { + return nil, err + } + + return config, nil +} + /////////////////// Config V1 /////////////////// type configV1 struct { Version string `json:"version"` @@ -17,24 +50,12 @@ type configV1 struct { // loadConfigV1 load config func loadConfigV1() (*configV1, error) { - configPath, err := getConfigPath() - if err != nil { + configFile := filepath.Join(getConfigDir(), "fsUsers.json") + config, err := loadOldConfig(configFile, &configV1{Version: "1"}) + if config == nil { return nil, err } - configFile := filepath.Join(configPath, "fsUsers.json") - if _, err = os.Stat(configFile); err != nil { - return nil, err - } - c := &configV1{} - c.Version = "1" - qc, err := quick.New(c) - if err != nil { - return nil, err - } - if err := qc.Load(configFile); err != nil { - return nil, err - } - return c, nil + return config.(*configV1), err } /////////////////// Config V2 /////////////////// @@ -61,23 +82,12 @@ type configV2 struct { // loadConfigV2 load config version '2'. func loadConfigV2() (*configV2, error) { - configFile, err := getConfigFile() - if err != nil { + configFile := getConfigFile() + config, err := loadOldConfig(configFile, &configV2{Version: "2"}) + if config == nil { return nil, err } - if _, err = os.Stat(configFile); err != nil { - return nil, err - } - c := &configV2{} - c.Version = "2" - qc, err := quick.New(c) - if err != nil { - return nil, err - } - if err := qc.Load(configFile); err != nil { - return nil, err - } - return c, nil + return config.(*configV2), err } /////////////////// Config V3 /////////////////// @@ -135,23 +145,12 @@ type configV3 struct { // loadConfigV3 load config version '3'. func loadConfigV3() (*configV3, error) { - configFile, err := getConfigFile() - if err != nil { + configFile := getConfigFile() + config, err := loadOldConfig(configFile, &configV3{Version: "3"}) + if config == nil { return nil, err } - if _, err = os.Stat(configFile); err != nil { - return nil, err - } - c := &configV3{} - c.Version = "3" - qc, err := quick.New(c) - if err != nil { - return nil, err - } - if err := qc.Load(configFile); err != nil { - return nil, err - } - return c, nil + return config.(*configV3), err } // logger type representing version '4' logger config. @@ -186,23 +185,12 @@ type configV4 struct { // loadConfigV4 load config version '4'. func loadConfigV4() (*configV4, error) { - configFile, err := getConfigFile() - if err != nil { + configFile := getConfigFile() + config, err := loadOldConfig(configFile, &configV4{Version: "4"}) + if config == nil { return nil, err } - if _, err = os.Stat(configFile); err != nil { - return nil, err - } - c := &configV4{} - c.Version = "4" - qc, err := quick.New(c) - if err != nil { - return nil, err - } - if err := qc.Load(configFile); err != nil { - return nil, err - } - return c, nil + return config.(*configV4), err } // logger type representing version '5' logger config. @@ -264,23 +252,12 @@ type configV5 struct { // loadConfigV5 load config version '5'. func loadConfigV5() (*configV5, error) { - configFile, err := getConfigFile() - if err != nil { + configFile := getConfigFile() + config, err := loadOldConfig(configFile, &configV5{Version: "5"}) + if config == nil { return nil, err } - if _, err = os.Stat(configFile); err != nil { - return nil, err - } - c := &configV5{} - c.Version = "5" - qc, err := quick.New(c) - if err != nil { - return nil, err - } - if err := qc.Load(configFile); err != nil { - return nil, err - } - return c, nil + return config.(*configV5), err } type loggerV6 struct { @@ -306,23 +283,12 @@ type configV6 struct { // loadConfigV6 load config version '6'. func loadConfigV6() (*configV6, error) { - configFile, err := getConfigFile() - if err != nil { + configFile := getConfigFile() + config, err := loadOldConfig(configFile, &configV6{Version: "6"}) + if config == nil { return nil, err } - if _, err = os.Stat(configFile); err != nil { - return nil, err - } - c := &configV6{} - c.Version = "6" - qc, err := quick.New(c) - if err != nil { - return nil, err - } - if err := qc.Load(configFile); err != nil { - return nil, err - } - return c, nil + return config.(*configV6), err } // Notifier represents collection of supported notification queues in version @@ -367,23 +333,12 @@ type serverConfigV7 struct { // loadConfigV7 load config version '7'. func loadConfigV7() (*serverConfigV7, error) { - configFile, err := getConfigFile() - if err != nil { + configFile := getConfigFile() + config, err := loadOldConfig(configFile, &serverConfigV7{Version: "7"}) + if config == nil { return nil, err } - if _, err = os.Stat(configFile); err != nil { - return nil, err - } - c := &serverConfigV7{} - c.Version = "7" - qc, err := quick.New(c) - if err != nil { - return nil, err - } - if err := qc.Load(configFile); err != nil { - return nil, err - } - return c, nil + return config.(*serverConfigV7), err } // serverConfigV8 server configuration version '8'. Adds NATS notifier @@ -407,23 +362,12 @@ type serverConfigV8 struct { // loadConfigV8 load config version '8'. func loadConfigV8() (*serverConfigV8, error) { - configFile, err := getConfigFile() - if err != nil { + configFile := getConfigFile() + config, err := loadOldConfig(configFile, &serverConfigV8{Version: "8"}) + if config == nil { return nil, err } - if _, err = os.Stat(configFile); err != nil { - return nil, err - } - c := &serverConfigV8{} - c.Version = "8" - qc, err := quick.New(c) - if err != nil { - return nil, err - } - if err := qc.Load(configFile); err != nil { - return nil, err - } - return c, nil + return config.(*serverConfigV8), err } // serverConfigV9 server configuration version '9'. Adds PostgreSQL @@ -446,24 +390,12 @@ type serverConfigV9 struct { } func loadConfigV9() (*serverConfigV9, error) { - configFile, err := getConfigFile() - if err != nil { + configFile := getConfigFile() + config, err := loadOldConfig(configFile, &serverConfigV9{Version: "9"}) + if config == nil { return nil, err } - if _, err = os.Stat(configFile); err != nil { - return nil, err - } - srvCfg := &serverConfigV9{} - srvCfg.Version = "9" - srvCfg.rwMutex = &sync.RWMutex{} - qc, err := quick.New(srvCfg) - if err != nil { - return nil, err - } - if err := qc.Load(configFile); err != nil { - return nil, err - } - return srvCfg, nil + return config.(*serverConfigV9), err } // serverConfigV10 server configuration version '10' which is like @@ -484,23 +416,12 @@ type serverConfigV10 struct { } func loadConfigV10() (*serverConfigV10, error) { - configFile, err := getConfigFile() - if err != nil { + configFile := getConfigFile() + config, err := loadOldConfig(configFile, &serverConfigV10{Version: "10"}) + if config == nil { return nil, err } - if _, err = os.Stat(configFile); err != nil { - return nil, err - } - srvCfg := &serverConfigV10{} - srvCfg.Version = "10" - qc, err := quick.New(srvCfg) - if err != nil { - return nil, err - } - if err := qc.Load(configFile); err != nil { - return nil, err - } - return srvCfg, nil + return config.(*serverConfigV10), err } // natsNotifyV1 - structure was valid until config V 11 @@ -532,23 +453,12 @@ type serverConfigV11 struct { } func loadConfigV11() (*serverConfigV11, error) { - configFile, err := getConfigFile() - if err != nil { + configFile := getConfigFile() + config, err := loadOldConfig(configFile, &serverConfigV11{Version: "11"}) + if config == nil { return nil, err } - if _, err = os.Stat(configFile); err != nil { - return nil, err - } - srvCfg := &serverConfigV11{} - srvCfg.Version = "11" - qc, err := quick.New(srvCfg) - if err != nil { - return nil, err - } - if err := qc.Load(configFile); err != nil { - return nil, err - } - return srvCfg, nil + return config.(*serverConfigV11), err } // serverConfigV12 server configuration version '12' which is like @@ -568,21 +478,35 @@ type serverConfigV12 struct { } func loadConfigV12() (*serverConfigV12, error) { - configFile, err := getConfigFile() - if err != nil { + configFile := getConfigFile() + config, err := loadOldConfig(configFile, &serverConfigV12{Version: "12"}) + if config == nil { return nil, err } - if _, err = os.Stat(configFile); err != nil { - return nil, err - } - srvCfg := &serverConfigV12{} - srvCfg.Version = "12" - qc, err := quick.New(srvCfg) - if err != nil { - return nil, err - } - if err := qc.Load(configFile); err != nil { - return nil, err - } - return srvCfg, nil + return config.(*serverConfigV12), err +} + +// serverConfigV13 server configuration version '13' which is like +// version '12' except it adds support for webhook notification. +type serverConfigV13 struct { + Version string `json:"version"` + + // S3 API configuration. + Credential credential `json:"credential"` + Region string `json:"region"` + + // Additional error logging configuration. + Logger *logger `json:"logger"` + + // Notification queue configuration. + Notify *notifier `json:"notify"` +} + +func loadConfigV13() (*serverConfigV13, error) { + configFile := getConfigFile() + config, err := loadOldConfig(configFile, &serverConfigV13{Version: "13"}) + if config == nil { + return nil, err + } + return config.(*serverConfigV13), err } diff --git a/cmd/config-v13.go b/cmd/config-v13.go deleted file mode 100644 index f1ee32b90..000000000 --- a/cmd/config-v13.go +++ /dev/null @@ -1,206 +0,0 @@ -/* - * Minio Cloud Storage, (C) 2016, 2017 Minio, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cmd - -import ( - "os" - "sync" - - "github.com/minio/minio/pkg/quick" -) - -// Read Write mutex for safe access to ServerConfig. -var serverConfigMu sync.RWMutex - -// serverConfigV13 server configuration version '13' which is like -// version '12' except it adds support for webhook notification. -type serverConfigV13 struct { - Version string `json:"version"` - - // S3 API configuration. - Credential credential `json:"credential"` - Region string `json:"region"` - - // Additional error logging configuration. - Logger *logger `json:"logger"` - - // Notification queue configuration. - Notify *notifier `json:"notify"` -} - -// newConfig - initialize a new server config, saves creds from env -// if globalIsEnvCreds is set otherwise generates a new set of keys -// and those are saved. -func newConfig(envCreds credential) error { - // Initialize server config. - srvCfg := &serverConfigV13{ - Logger: &logger{}, - Notify: ¬ifier{}, - } - srvCfg.Version = globalMinioConfigVersion - srvCfg.Region = globalMinioDefaultRegion - - // If env is set for a fresh start, save them to config file. - if globalIsEnvCreds { - srvCfg.SetCredential(envCreds) - } else { - srvCfg.SetCredential(newCredential()) - } - - // Enable console logger by default on a fresh run. - srvCfg.Logger.Console = consoleLogger{ - Enable: true, - Level: "error", - } - - // Make sure to initialize notification configs. - srvCfg.Notify.AMQP = make(map[string]amqpNotify) - srvCfg.Notify.AMQP["1"] = amqpNotify{} - srvCfg.Notify.ElasticSearch = make(map[string]elasticSearchNotify) - srvCfg.Notify.ElasticSearch["1"] = elasticSearchNotify{} - srvCfg.Notify.Redis = make(map[string]redisNotify) - srvCfg.Notify.Redis["1"] = redisNotify{} - srvCfg.Notify.NATS = make(map[string]natsNotify) - srvCfg.Notify.NATS["1"] = natsNotify{} - srvCfg.Notify.PostgreSQL = make(map[string]postgreSQLNotify) - srvCfg.Notify.PostgreSQL["1"] = postgreSQLNotify{} - srvCfg.Notify.Kafka = make(map[string]kafkaNotify) - srvCfg.Notify.Kafka["1"] = kafkaNotify{} - srvCfg.Notify.Webhook = make(map[string]webhookNotify) - srvCfg.Notify.Webhook["1"] = webhookNotify{} - - // Create config path. - if err := createConfigPath(); err != nil { - return err - } - - // hold the mutex lock before a new config is assigned. - // Save the new config globally. - // unlock the mutex. - serverConfigMu.Lock() - serverConfig = srvCfg - serverConfigMu.Unlock() - - // Save config into file. - return serverConfig.Save() -} - -// loadConfig - loads a new config from disk, overrides creds from env -// if globalIsEnvCreds is set otherwise serves the creds from loaded -// from the disk. -func loadConfig(envCreds credential) error { - configFile, err := getConfigFile() - if err != nil { - return err - } - - if _, err = os.Stat(configFile); err != nil { - return err - } - srvCfg := &serverConfigV13{} - srvCfg.Version = globalMinioConfigVersion - qc, err := quick.New(srvCfg) - if err != nil { - return err - } - - if err = qc.Load(configFile); err != nil { - return err - } - - // If env is set override the credentials from config file. - if globalIsEnvCreds { - srvCfg.SetCredential(envCreds) - } else { - srvCfg.SetCredential(srvCfg.Credential) - } - - // hold the mutex lock before a new config is assigned. - serverConfigMu.Lock() - // Save the loaded config globally. - serverConfig = srvCfg - serverConfigMu.Unlock() - - // Set the version properly after the unmarshalled json is loaded. - serverConfig.Version = globalMinioConfigVersion - return nil -} - -// serverConfig server config. -var serverConfig *serverConfigV13 - -// GetVersion get current config version. -func (s serverConfigV13) GetVersion() string { - serverConfigMu.RLock() - defer serverConfigMu.RUnlock() - - return s.Version -} - -// SetRegion set new region. -func (s *serverConfigV13) SetRegion(region string) { - serverConfigMu.Lock() - defer serverConfigMu.Unlock() - - s.Region = region -} - -// GetRegion get current region. -func (s serverConfigV13) GetRegion() string { - serverConfigMu.RLock() - defer serverConfigMu.RUnlock() - - return s.Region -} - -// SetCredentials set new credentials. -func (s *serverConfigV13) SetCredential(creds credential) { - serverConfigMu.Lock() - defer serverConfigMu.Unlock() - - // Set updated credential. - s.Credential = newCredentialWithKeys(creds.AccessKey, creds.SecretKey) -} - -// GetCredentials get current credentials. -func (s serverConfigV13) GetCredential() credential { - serverConfigMu.RLock() - defer serverConfigMu.RUnlock() - - return s.Credential -} - -// Save config. -func (s serverConfigV13) Save() error { - serverConfigMu.RLock() - defer serverConfigMu.RUnlock() - - // get config file. - configFile, err := getConfigFile() - if err != nil { - return err - } - - // initialize quick. - qc, err := quick.New(&s) - if err != nil { - return err - } - - // Save config file. - return qc.Save(configFile) -} diff --git a/cmd/config-v13_test.go b/cmd/config-v13_test.go deleted file mode 100644 index 4b4e420f9..000000000 --- a/cmd/config-v13_test.go +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Minio Cloud Storage, (C) 2016, 2017 Minio, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cmd - -import ( - "reflect" - "testing" -) - -func TestServerConfig(t *testing.T) { - rootPath, err := newTestConfig(globalMinioDefaultRegion) - if err != nil { - t.Fatalf("Init Test config failed") - } - // remove the root directory after the test ends. - defer removeAll(rootPath) - - if serverConfig.GetRegion() != globalMinioDefaultRegion { - t.Errorf("Expecting region `us-east-1` found %s", serverConfig.GetRegion()) - } - - // Set new region and verify. - serverConfig.SetRegion("us-west-1") - if serverConfig.GetRegion() != "us-west-1" { - t.Errorf("Expecting region `us-west-1` found %s", serverConfig.GetRegion()) - } - - // Set new amqp notification id. - serverConfig.Notify.SetAMQPByID("2", amqpNotify{}) - savedNotifyCfg1 := serverConfig.Notify.GetAMQPByID("2") - if !reflect.DeepEqual(savedNotifyCfg1, amqpNotify{}) { - t.Errorf("Expecting AMQP config %#v found %#v", amqpNotify{}, savedNotifyCfg1) - } - - // Set new elastic search notification id. - serverConfig.Notify.SetElasticSearchByID("2", elasticSearchNotify{}) - savedNotifyCfg2 := serverConfig.Notify.GetElasticSearchByID("2") - if !reflect.DeepEqual(savedNotifyCfg2, elasticSearchNotify{}) { - t.Errorf("Expecting Elasticsearch config %#v found %#v", elasticSearchNotify{}, savedNotifyCfg2) - } - - // Set new redis notification id. - serverConfig.Notify.SetRedisByID("2", redisNotify{}) - savedNotifyCfg3 := serverConfig.Notify.GetRedisByID("2") - if !reflect.DeepEqual(savedNotifyCfg3, redisNotify{}) { - t.Errorf("Expecting Redis config %#v found %#v", redisNotify{}, savedNotifyCfg3) - } - - // Set new kafka notification id. - serverConfig.Notify.SetKafkaByID("2", kafkaNotify{}) - savedNotifyCfg4 := serverConfig.Notify.GetKafkaByID("2") - if !reflect.DeepEqual(savedNotifyCfg4, kafkaNotify{}) { - t.Errorf("Expecting Kafka config %#v found %#v", kafkaNotify{}, savedNotifyCfg4) - } - - // Set new Webhook notification id. - serverConfig.Notify.SetWebhookByID("2", webhookNotify{}) - savedNotifyCfg5 := serverConfig.Notify.GetWebhookByID("2") - if !reflect.DeepEqual(savedNotifyCfg5, webhookNotify{}) { - t.Errorf("Expecting Webhook config %#v found %#v", webhookNotify{}, savedNotifyCfg3) - } - - // Set new console logger. - serverConfig.Logger.SetConsole(consoleLogger{ - Enable: true, - }) - consoleCfg := serverConfig.Logger.GetConsole() - if !reflect.DeepEqual(consoleCfg, consoleLogger{Enable: true}) { - t.Errorf("Expecting console logger config %#v found %#v", consoleLogger{Enable: true}, consoleCfg) - } - // Set new console logger. - serverConfig.Logger.SetConsole(consoleLogger{ - Enable: false, - }) - - // Set new file logger. - serverConfig.Logger.SetFile(fileLogger{ - Enable: true, - }) - fileCfg := serverConfig.Logger.GetFile() - if !reflect.DeepEqual(fileCfg, fileLogger{Enable: true}) { - t.Errorf("Expecting file logger config %#v found %#v", fileLogger{Enable: true}, consoleCfg) - } - // Set new file logger. - serverConfig.Logger.SetFile(fileLogger{ - Enable: false, - }) - - // Match version. - if serverConfig.GetVersion() != globalMinioConfigVersion { - t.Errorf("Expecting version %s found %s", serverConfig.GetVersion(), globalMinioConfigVersion) - } - - // Attempt to save. - if err := serverConfig.Save(); err != nil { - t.Fatalf("Unable to save updated config file %s", err) - } - - // Do this only once here. - setGlobalConfigPath(rootPath) - - // Initialize server config. - if err := loadConfig(credential{}); err != nil { - t.Fatalf("Unable to initialize from updated config file %s", err) - } -} diff --git a/cmd/config-v14.go b/cmd/config-v14.go new file mode 100644 index 000000000..e17d2b787 --- /dev/null +++ b/cmd/config-v14.go @@ -0,0 +1,370 @@ +/* + * Minio Cloud Storage, (C) 2016, 2017 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cmd + +import ( + "errors" + "fmt" + "io/ioutil" + "os" + "strings" + "sync" + + "github.com/minio/minio/pkg/quick" + "github.com/tidwall/gjson" +) + +// Read Write mutex for safe access to ServerConfig. +var serverConfigMu sync.RWMutex + +// Config version +var v14 = "14" + +// serverConfigV14 server configuration version '14' which is like +// version '13' except it adds support of browser param. +type serverConfigV14 struct { + Version string `json:"version"` + + // S3 API configuration. + Credential credential `json:"credential"` + Region string `json:"region"` + Browser string `json:"browser"` + + // Additional error logging configuration. + Logger *logger `json:"logger"` + + // Notification queue configuration. + Notify *notifier `json:"notify"` +} + +func newServerConfigV14() *serverConfigV14 { + srvCfg := &serverConfigV14{ + Version: v14, + Region: globalMinioDefaultRegion, + Logger: &logger{}, + Notify: ¬ifier{}, + } + srvCfg.SetCredential(mustGetNewCredential()) + srvCfg.SetBrowser("on") + // Enable console logger by default on a fresh run. + srvCfg.Logger.Console = consoleLogger{ + Enable: true, + Level: "error", + } + + // Make sure to initialize notification configs. + srvCfg.Notify.AMQP = make(map[string]amqpNotify) + srvCfg.Notify.AMQP["1"] = amqpNotify{} + srvCfg.Notify.ElasticSearch = make(map[string]elasticSearchNotify) + srvCfg.Notify.ElasticSearch["1"] = elasticSearchNotify{} + srvCfg.Notify.Redis = make(map[string]redisNotify) + srvCfg.Notify.Redis["1"] = redisNotify{} + srvCfg.Notify.NATS = make(map[string]natsNotify) + srvCfg.Notify.NATS["1"] = natsNotify{} + srvCfg.Notify.PostgreSQL = make(map[string]postgreSQLNotify) + srvCfg.Notify.PostgreSQL["1"] = postgreSQLNotify{} + srvCfg.Notify.Kafka = make(map[string]kafkaNotify) + srvCfg.Notify.Kafka["1"] = kafkaNotify{} + srvCfg.Notify.Webhook = make(map[string]webhookNotify) + srvCfg.Notify.Webhook["1"] = webhookNotify{} + + return srvCfg +} + +// newConfig - initialize a new server config, saves env parameters if +// found, otherwise use default parameters +func newConfig(envParams envParams) error { + // Initialize server config. + srvCfg := newServerConfigV14() + + // If env is set for a fresh start, save them to config file. + if globalIsEnvCreds { + srvCfg.SetCredential(envParams.creds) + } + + if globalIsEnvBrowser { + srvCfg.SetBrowser(envParams.browser) + } + + // Create config path. + if err := createConfigDir(); err != nil { + return err + } + + // hold the mutex lock before a new config is assigned. + // Save the new config globally. + // unlock the mutex. + serverConfigMu.Lock() + serverConfig = srvCfg + serverConfigMu.Unlock() + + // Save config into file. + return serverConfig.Save() +} + +// loadConfig - loads a new config from disk, overrides params from env +// if found and valid +func loadConfig(envParams envParams) error { + configFile := getConfigFile() + if _, err := os.Stat(configFile); err != nil { + return err + } + + srvCfg := &serverConfigV14{} + + qc, err := quick.New(srvCfg) + if err != nil { + return err + } + + if err = qc.Load(configFile); err != nil { + return err + } + + // If env is set override the credentials from config file. + if globalIsEnvCreds { + srvCfg.SetCredential(envParams.creds) + } + + if globalIsEnvBrowser { + srvCfg.SetBrowser(envParams.browser) + } + + if strings.ToLower(srvCfg.GetBrowser()) == "off" { + globalIsBrowserEnabled = false + } + + // hold the mutex lock before a new config is assigned. + serverConfigMu.Lock() + // Save the loaded config globally. + serverConfig = srvCfg + serverConfigMu.Unlock() + + if serverConfig.Version != v14 { + return errors.New("Unsupported config version `" + serverConfig.Version + "`.") + } + + return nil +} + +// doCheckDupJSONKeys recursively detects duplicate json keys +func doCheckDupJSONKeys(key, value gjson.Result) error { + // Key occurrences map of the current scope to count + // if there is any duplicated json key. + keysOcc := make(map[string]int) + + // Holds the found error + var checkErr error + + // Iterate over keys in the current json scope + value.ForEach(func(k, v gjson.Result) bool { + // If current key is not null, check if its + // value contains some duplicated keys. + if k.Type != gjson.Null { + keysOcc[k.String()]++ + checkErr = doCheckDupJSONKeys(k, v) + } + return checkErr == nil + }) + + // Check found err + if checkErr != nil { + return errors.New(key.String() + " => " + checkErr.Error()) + } + + // Check for duplicated keys + for k, v := range keysOcc { + if v > 1 { + return errors.New(key.String() + " => `" + k + "` entry is duplicated") + } + } + + return nil +} + +// Check recursively if a key is duplicated in the same json scope +// e.g.: +// `{ "key" : { "key" ..` is accepted +// `{ "key" : { "subkey" : "val1", "subkey": "val2" ..` throws subkey duplicated error +func checkDupJSONKeys(json string) error { + // Parse config with gjson library + config := gjson.Parse(json) + + // Create a fake rootKey since root json doesn't seem to have representation + // in gjson library. + rootKey := gjson.Result{Type: gjson.String, Str: minioConfigFile} + + // Check if loaded json contains any duplicated keys + return doCheckDupJSONKeys(rootKey, config) +} + +// validateConfig checks for +func validateConfig() error { + + // Get file config path + configFile := getConfigFile() + + srvCfg := &serverConfigV14{} + + // Load config file + qc, err := quick.New(srvCfg) + if err != nil { + return err + } + if err = qc.Load(configFile); err != nil { + return err + } + + // Check if config version is valid + if srvCfg.GetVersion() != v14 { + return errors.New("bad config version, expected: " + v14) + } + + // Load config file json and check for duplication json keys + jsonBytes, err := ioutil.ReadFile(configFile) + if err != nil { + return err + } + if err := checkDupJSONKeys(string(jsonBytes)); err != nil { + return err + } + + // Validate region field + if srvCfg.GetRegion() == "" { + return errors.New("Region config value cannot be empty") + } + + // Validate browser field + if b := strings.ToLower(srvCfg.GetBrowser()); b != "on" && b != "off" { + return fmt.Errorf("Browser config value %s is invalid", b) + } + + // Validate credential field + if !srvCfg.Credential.IsValid() { + return errors.New("invalid credential") + } + + // Validate logger field + if err := srvCfg.Logger.Validate(); err != nil { + return err + } + + // Validate notify field + if err := srvCfg.Notify.Validate(); err != nil { + return err + } + + return nil +} + +// serverConfig server config. +var serverConfig *serverConfigV14 + +// GetVersion get current config version. +func (s serverConfigV14) GetVersion() string { + serverConfigMu.RLock() + defer serverConfigMu.RUnlock() + + return s.Version +} + +// SetRegion set new region. +func (s *serverConfigV14) SetRegion(region string) { + serverConfigMu.Lock() + defer serverConfigMu.Unlock() + + // Empty region means "us-east-1" by default from S3 spec. + if region == "" { + region = "us-east-1" + } + s.Region = region +} + +// GetRegion get current region. +func (s serverConfigV14) GetRegion() string { + serverConfigMu.RLock() + defer serverConfigMu.RUnlock() + + if s.Region != "" { + return s.Region + } // region empty + + // Empty region means "us-east-1" by default from S3 spec. + return "us-east-1" +} + +// SetCredentials set new credentials. +func (s *serverConfigV14) SetCredential(creds credential) { + serverConfigMu.Lock() + defer serverConfigMu.Unlock() + + // Set updated credential. + s.Credential = creds +} + +// GetCredentials get current credentials. +func (s serverConfigV14) GetCredential() credential { + serverConfigMu.RLock() + defer serverConfigMu.RUnlock() + + return s.Credential +} + +// SetBrowser set if browser is enabled. +func (s *serverConfigV14) SetBrowser(v string) { + serverConfigMu.Lock() + defer serverConfigMu.Unlock() + + // Set browser param + if v == "" { + v = "on" // Browser is on by default. + } + + // Set the new value. + s.Browser = v +} + +// GetCredentials get current credentials. +func (s serverConfigV14) GetBrowser() string { + serverConfigMu.RLock() + defer serverConfigMu.RUnlock() + + if s.Browser != "" { + return s.Browser + } // empty browser. + + // Empty browser means "on" by default. + return "on" +} + +// Save config. +func (s serverConfigV14) Save() error { + serverConfigMu.RLock() + defer serverConfigMu.RUnlock() + + // get config file. + configFile := getConfigFile() + + // initialize quick. + qc, err := quick.New(&s) + if err != nil { + return err + } + + // Save config file. + return qc.Save(configFile) +} diff --git a/cmd/config-v14_test.go b/cmd/config-v14_test.go new file mode 100644 index 000000000..718b6276d --- /dev/null +++ b/cmd/config-v14_test.go @@ -0,0 +1,292 @@ +/* + * Minio Cloud Storage, (C) 2016, 2017 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cmd + +import ( + "io/ioutil" + "os" + "path/filepath" + "reflect" + "testing" + + "github.com/tidwall/gjson" +) + +func TestServerConfig(t *testing.T) { + rootPath, err := newTestConfig(globalMinioDefaultRegion) + if err != nil { + t.Fatalf("Init Test config failed") + } + // remove the root directory after the test ends. + defer removeAll(rootPath) + + if serverConfig.GetRegion() != globalMinioDefaultRegion { + t.Errorf("Expecting region `us-east-1` found %s", serverConfig.GetRegion()) + } + + // Set new region and verify. + serverConfig.SetRegion("us-west-1") + if serverConfig.GetRegion() != "us-west-1" { + t.Errorf("Expecting region `us-west-1` found %s", serverConfig.GetRegion()) + } + + // Set new amqp notification id. + serverConfig.Notify.SetAMQPByID("2", amqpNotify{}) + savedNotifyCfg1 := serverConfig.Notify.GetAMQPByID("2") + if !reflect.DeepEqual(savedNotifyCfg1, amqpNotify{}) { + t.Errorf("Expecting AMQP config %#v found %#v", amqpNotify{}, savedNotifyCfg1) + } + + // Set new elastic search notification id. + serverConfig.Notify.SetElasticSearchByID("2", elasticSearchNotify{}) + savedNotifyCfg2 := serverConfig.Notify.GetElasticSearchByID("2") + if !reflect.DeepEqual(savedNotifyCfg2, elasticSearchNotify{}) { + t.Errorf("Expecting Elasticsearch config %#v found %#v", elasticSearchNotify{}, savedNotifyCfg2) + } + + // Set new redis notification id. + serverConfig.Notify.SetRedisByID("2", redisNotify{}) + savedNotifyCfg3 := serverConfig.Notify.GetRedisByID("2") + if !reflect.DeepEqual(savedNotifyCfg3, redisNotify{}) { + t.Errorf("Expecting Redis config %#v found %#v", redisNotify{}, savedNotifyCfg3) + } + + // Set new kafka notification id. + serverConfig.Notify.SetKafkaByID("2", kafkaNotify{}) + savedNotifyCfg4 := serverConfig.Notify.GetKafkaByID("2") + if !reflect.DeepEqual(savedNotifyCfg4, kafkaNotify{}) { + t.Errorf("Expecting Kafka config %#v found %#v", kafkaNotify{}, savedNotifyCfg4) + } + + // Set new Webhook notification id. + serverConfig.Notify.SetWebhookByID("2", webhookNotify{}) + savedNotifyCfg5 := serverConfig.Notify.GetWebhookByID("2") + if !reflect.DeepEqual(savedNotifyCfg5, webhookNotify{}) { + t.Errorf("Expecting Webhook config %#v found %#v", webhookNotify{}, savedNotifyCfg3) + } + + // Set new console logger. + serverConfig.Logger.SetConsole(consoleLogger{ + Enable: true, + }) + consoleCfg := serverConfig.Logger.GetConsole() + if !reflect.DeepEqual(consoleCfg, consoleLogger{Enable: true}) { + t.Errorf("Expecting console logger config %#v found %#v", consoleLogger{Enable: true}, consoleCfg) + } + // Set new console logger. + serverConfig.Logger.SetConsole(consoleLogger{ + Enable: false, + }) + + // Set new file logger. + serverConfig.Logger.SetFile(fileLogger{ + Enable: true, + }) + fileCfg := serverConfig.Logger.GetFile() + if !reflect.DeepEqual(fileCfg, fileLogger{Enable: true}) { + t.Errorf("Expecting file logger config %#v found %#v", fileLogger{Enable: true}, consoleCfg) + } + // Set new file logger. + serverConfig.Logger.SetFile(fileLogger{ + Enable: false, + }) + + // Match version. + if serverConfig.GetVersion() != v14 { + t.Errorf("Expecting version %s found %s", serverConfig.GetVersion(), v14) + } + + // Attempt to save. + if err := serverConfig.Save(); err != nil { + t.Fatalf("Unable to save updated config file %s", err) + } + + // Do this only once here. + setConfigDir(rootPath) + + // Initialize server config. + if err := loadConfig(envParams{}); err != nil { + t.Fatalf("Unable to initialize from updated config file %s", err) + } +} + +func TestServerConfigWithEnvs(t *testing.T) { + + os.Setenv("MINIO_BROWSER", "off") + defer os.Unsetenv("MINIO_BROWSER") + + os.Setenv("MINIO_ACCESS_KEY", "minio") + defer os.Unsetenv("MINIO_ACCESS_KEY") + + os.Setenv("MINIO_SECRET_KEY", "minio123") + defer os.Unsetenv("MINIO_SECRET_KEY") + + defer func() { + globalIsEnvBrowser = false + globalIsEnvCreds = false + }() + + // Get test root. + rootPath, err := getTestRoot() + if err != nil { + t.Error(err) + } + + // Do this only once here. + setConfigDir(rootPath) + + // Init config + initConfig() + + // remove the root directory after the test ends. + defer removeAll(rootPath) + + // Check if serverConfig has + if serverConfig.GetBrowser() != "off" { + t.Errorf("Expecting browser `off` found %s", serverConfig.GetBrowser()) + } + + // Check if serverConfig has + cred := serverConfig.GetCredential() + + if cred.AccessKey != "minio" { + t.Errorf("Expecting access key to be `minio` found %s", cred.AccessKey) + } + + if cred.SecretKey != "minio123" { + t.Errorf("Expecting access key to be `minio123` found %s", cred.SecretKey) + } +} + +func TestCheckDupJSONKeys(t *testing.T) { + testCases := []struct { + json string + shouldPass bool + }{ + {`{}`, true}, + {`{"version" : "13"}`, true}, + {`{"version" : "13", "version": "14"}`, false}, + {`{"version" : "13", "credential": {"accessKey": "12345"}}`, true}, + {`{"version" : "13", "credential": {"accessKey": "12345", "accessKey":"12345"}}`, false}, + {`{"version" : "13", "notify": {"amqp": {"1"}, "webhook":{"3"}}}`, true}, + {`{"version" : "13", "notify": {"amqp": {"1"}, "amqp":{"3"}}}`, false}, + {`{"version" : "13", "notify": {"amqp": {"1":{}, "2":{}}}}`, true}, + {`{"version" : "13", "notify": {"amqp": {"1":{}, "1":{}}}}`, false}, + } + + for i, testCase := range testCases { + err := doCheckDupJSONKeys(gjson.Result{}, gjson.Parse(testCase.json)) + if testCase.shouldPass && err != nil { + t.Errorf("Test %d, should pass but it failed with err = %v", i+1, err) + } + if !testCase.shouldPass && err == nil { + t.Errorf("Test %d, should fail but it succeed.", i+1) + } + } + +} + +// Tests config validator.. +func TestValidateConfig(t *testing.T) { + rootPath, err := newTestConfig(globalMinioDefaultRegion) + if err != nil { + t.Fatalf("Init Test config failed") + } + + // remove the root directory after the test ends. + defer removeAll(rootPath) + + configPath := filepath.Join(rootPath, minioConfigFile) + + v := v14 + + testCases := []struct { + configData string + shouldPass bool + }{ + // Test 1 - wrong json + {`{`, false}, + + // Test 2 - empty json + {`{}`, false}, + + // Test 3 - wrong config version + {`{"version": "10"}`, false}, + + // Test 4 - wrong browser parameter + {`{"version": "` + v + `", "browser": "foo"}`, false}, + + // Test 5 - missing credential + {`{"version": "` + v + `", "browser": "on"}`, false}, + + // Test 6 - missing secret key + {`{"version": "` + v + `", "browser": "on", "credential" : {"accessKey":"minio", "secretKey":""}}`, false}, + + // Test 7 - missing region should pass, defaults to 'us-east-1'. + {`{"version": "` + v + `", "browser": "on", "credential" : {"accessKey":"minio", "secretKey":"minio123"}}`, true}, + + // Test 8 - missing browser should pass, defaults to 'on'. + {`{"version": "` + v + `", "region": "us-east-1", "credential" : {"accessKey":"minio", "secretKey":"minio123"}}`, true}, + + // Test 9 - success + {`{"version": "` + v + `", "browser": "on", "region":"us-east-1", "credential" : {"accessKey":"minio", "secretKey":"minio123"}}`, true}, + + // Test 10 - duplicated json keys + {`{"version": "` + v + `", "browser": "on", "browser": "on", "region":"us-east-1", "credential" : {"accessKey":"minio", "secretKey":"minio123"}}`, false}, + + // Test 11 - Wrong Console logger level + {`{"version": "` + v + `", "credential": { "accessKey": "minio", "secretKey": "minio123" }, "region": "us-east-1", "browser": "on", "logger": { "console": { "enable": true, "level": "foo" } }}`, false}, + + // Test 12 - Wrong File logger level + {`{"version": "` + v + `", "credential": { "accessKey": "minio", "secretKey": "minio123" }, "region": "us-east-1", "browser": "on", "logger": { "file": { "enable": true, "level": "foo" } }}`, false}, + + // Test 13 - Test AMQP + {`{"version": "` + v + `", "credential": { "accessKey": "minio", "secretKey": "minio123" }, "region": "us-east-1", "browser": "on", "notify": { "amqp": { "1": { "enable": true, "url": "", "exchange": "", "routingKey": "", "exchangeType": "", "mandatory": false, "immediate": false, "durable": false, "internal": false, "noWait": false, "autoDeleted": false }}}}`, false}, + + // Test 14 - Test NATS + {`{"version": "` + v + `", "credential": { "accessKey": "minio", "secretKey": "minio123" }, "region": "us-east-1", "browser": "on", "notify": { "nats": { "1": { "enable": true, "address": "", "subject": "", "username": "", "password": "", "token": "", "secure": false, "pingInterval": 0, "streaming": { "enable": false, "clusterID": "", "clientID": "", "async": false, "maxPubAcksInflight": 0 } } }}}`, false}, + + // Test 15 - Test ElasticSearch + {`{"version": "` + v + `", "credential": { "accessKey": "minio", "secretKey": "minio123" }, "region": "us-east-1", "browser": "on", "notify": { "elasticsearch": { "1": { "enable": true, "url": "", "index": "" } }}}`, false}, + + // Test 16 - Test Redis + {`{"version": "` + v + `", "credential": { "accessKey": "minio", "secretKey": "minio123" }, "region": "us-east-1", "browser": "on", "notify": { "redis": { "1": { "enable": true, "address": "", "password": "", "key": "" } }}}`, false}, + + // Test 17 - Test PostgreSQL + {`{"version": "` + v + `", "credential": { "accessKey": "minio", "secretKey": "minio123" }, "region": "us-east-1", "browser": "on", "notify": { "postgresql": { "1": { "enable": true, "connectionString": "", "table": "", "host": "", "port": "", "user": "", "password": "", "database": "" }}}}`, false}, + + // Test 18 - Test Kafka + {`{"version": "` + v + `", "credential": { "accessKey": "minio", "secretKey": "minio123" }, "region": "us-east-1", "browser": "on", "notify": { "kafka": { "1": { "enable": true, "brokers": null, "topic": "" } }}}`, false}, + + // Test 19 - Test Webhook + {`{"version": "` + v + `", "credential": { "accessKey": "minio", "secretKey": "minio123" }, "region": "us-east-1", "browser": "on", "notify": { "webhook": { "1": { "enable": true, "endpoint": "" } }}}`, false}, + } + + for i, testCase := range testCases { + if werr := ioutil.WriteFile(configPath, []byte(testCase.configData), 0700); werr != nil { + t.Fatal(werr) + } + verr := validateConfig() + if testCase.shouldPass && verr != nil { + t.Errorf("Test %d, should pass but it failed with err = %v", i+1, verr) + } + if !testCase.shouldPass && verr == nil { + t.Errorf("Test %d, should fail but it succeed.", i+1) + } + } + +} diff --git a/cmd/config.go b/cmd/config.go deleted file mode 100644 index 618bc2388..000000000 --- a/cmd/config.go +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Minio Cloud Storage, (C) 2015, 2016 Minio, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cmd - -import ( - "os" - "path/filepath" - "sync" - - "github.com/minio/go-homedir" -) - -// configPath for custom config path only for testing purposes -var customConfigPath string -var configMu sync.Mutex - -// Sets a new config path. -func setGlobalConfigPath(configPath string) { - configMu.Lock() - defer configMu.Unlock() - customConfigPath = configPath -} - -// getConfigPath get server config path -func getConfigPath() (string, error) { - configMu.Lock() - defer configMu.Unlock() - - if customConfigPath != "" { - return customConfigPath, nil - } - homeDir, err := homedir.Dir() - if err != nil { - return "", err - } - configPath := filepath.Join(homeDir, globalMinioConfigDir) - return configPath, nil -} - -// mustGetConfigPath must get server config path. -func mustGetConfigPath() string { - configPath, err := getConfigPath() - if err != nil { - return "" - } - return configPath -} - -// createConfigPath create server config path. -func createConfigPath() error { - configPath, err := getConfigPath() - if err != nil { - return err - } - return os.MkdirAll(configPath, 0700) -} - -// isConfigFileExists - returns true if config file exists. -func isConfigFileExists() bool { - path, err := getConfigFile() - if err != nil { - return false - } - st, err := os.Stat(path) - // If file exists and is regular return true. - if err == nil && st.Mode().IsRegular() { - return true - } - return false -} - -// getConfigFile get server config file. -func getConfigFile() (string, error) { - configPath, err := getConfigPath() - if err != nil { - return "", err - } - return filepath.Join(configPath, globalMinioConfigFile), nil -} diff --git a/cmd/copy-part-range.go b/cmd/copy-part-range.go new file mode 100644 index 000000000..316eebf80 --- /dev/null +++ b/cmd/copy-part-range.go @@ -0,0 +1,106 @@ +/* + * Minio Cloud Storage, (C) 2017 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cmd + +import ( + "fmt" + "net/http" + "net/url" + "strconv" + "strings" +) + +// Writes S3 compatible copy part range error. +func writeCopyPartErr(w http.ResponseWriter, err error, url *url.URL) { + switch err { + case errInvalidRange: + writeErrorResponse(w, ErrInvalidCopyPartRange, url) + return + case errInvalidRangeSource: + writeErrorResponse(w, ErrInvalidCopyPartRangeSource, url) + return + default: + writeErrorResponse(w, ErrInternalError, url) + return + } +} + +// Parses x-amz-copy-source-range for CopyObjectPart API. Specifically written to +// differentiate the behavior between regular httpRange header v/s x-amz-copy-source-range. +// The range of bytes to copy from the source object. The range value must use the form +// bytes=first-last, where the first and last are the zero-based byte offsets to copy. +// For example, bytes=0-9 indicates that you want to copy the first ten bytes of the source. +// http://docs.aws.amazon.com/AmazonS3/latest/API/mpUploadUploadPartCopy.html +func parseCopyPartRange(rangeString string, resourceSize int64) (hrange *httpRange, err error) { + // Return error if given range string doesn't start with byte range prefix. + if !strings.HasPrefix(rangeString, byteRangePrefix) { + return nil, fmt.Errorf("'%s' does not start with '%s'", rangeString, byteRangePrefix) + } + + // Trim byte range prefix. + byteRangeString := strings.TrimPrefix(rangeString, byteRangePrefix) + + // Check if range string contains delimiter '-', else return error. eg. "bytes=8" + sepIndex := strings.Index(byteRangeString, "-") + if sepIndex == -1 { + return nil, errInvalidRange + } + + offsetBeginString := byteRangeString[:sepIndex] + offsetBegin := int64(-1) + // Convert offsetBeginString only if its not empty. + if len(offsetBeginString) > 0 { + if !validBytePos.MatchString(offsetBeginString) { + return nil, errInvalidRange + } + if offsetBegin, err = strconv.ParseInt(offsetBeginString, 10, 64); err != nil { + return nil, errInvalidRange + } + } + + offsetEndString := byteRangeString[sepIndex+1:] + offsetEnd := int64(-1) + // Convert offsetEndString only if its not empty. + if len(offsetEndString) > 0 { + if !validBytePos.MatchString(offsetEndString) { + return nil, errInvalidRange + } + if offsetEnd, err = strconv.ParseInt(offsetEndString, 10, 64); err != nil { + return nil, errInvalidRange + } + } + + // rangeString contains first byte positions. eg. "bytes=2-" or + // rangeString contains last bye positions. eg. "bytes=-2" + if offsetBegin == -1 || offsetEnd == -1 { + return nil, errInvalidRange + } + + // Last byte position should not be greater than first byte + // position. eg. "bytes=5-2" + if offsetBegin > offsetEnd { + return nil, errInvalidRange + } + + // First and last byte positions should not be >= resourceSize. + if offsetBegin >= resourceSize || offsetEnd >= resourceSize { + return nil, errInvalidRangeSource + } + + // Success.. + return &httpRange{offsetBegin, offsetEnd, resourceSize}, nil +} diff --git a/cmd/copy-part-range_test.go b/cmd/copy-part-range_test.go new file mode 100644 index 000000000..0c8d8f266 --- /dev/null +++ b/cmd/copy-part-range_test.go @@ -0,0 +1,85 @@ +/* + * Minio Cloud Storage, (C) 2017 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cmd + +import "testing" + +// Test parseCopyPartRange() +func TestParseCopyPartRange(t *testing.T) { + // Test success cases. + successCases := []struct { + rangeString string + offsetBegin int64 + offsetEnd int64 + length int64 + }{ + {"bytes=2-5", 2, 5, 4}, + {"bytes=2-9", 2, 9, 8}, + {"bytes=2-2", 2, 2, 1}, + {"bytes=0000-0006", 0, 6, 7}, + } + + for _, successCase := range successCases { + hrange, err := parseCopyPartRange(successCase.rangeString, 10) + if err != nil { + t.Fatalf("expected: , got: %s", err) + } + + if hrange.offsetBegin != successCase.offsetBegin { + t.Fatalf("expected: %d, got: %d", successCase.offsetBegin, hrange.offsetBegin) + } + + if hrange.offsetEnd != successCase.offsetEnd { + t.Fatalf("expected: %d, got: %d", successCase.offsetEnd, hrange.offsetEnd) + } + if hrange.getLength() != successCase.length { + t.Fatalf("expected: %d, got: %d", successCase.length, hrange.getLength()) + } + } + + // Test invalid range strings. + invalidRangeStrings := []string{ + "bytes=8", + "bytes=5-2", + "bytes=+2-5", + "bytes=2-+5", + "bytes=2--5", + "bytes=-", + "", + "2-5", + "bytes = 2-5", + "bytes=2 - 5", + "bytes=0-0,-1", + "bytes=2-5 ", + } + for _, rangeString := range invalidRangeStrings { + if _, err := parseCopyPartRange(rangeString, 10); err == nil { + t.Fatalf("expected: an error, got: for range %s", rangeString) + } + } + + // Test error range strings. + errorRangeString := []string{ + "bytes=10-10", + "bytes=20-30", + } + for _, rangeString := range errorRangeString { + if _, err := parseCopyPartRange(rangeString, 10); err != errInvalidRangeSource { + t.Fatalf("expected: %s, got: %s", errInvalidRangeSource, err) + } + } +} diff --git a/cmd/credential.go b/cmd/credential.go index c75049050..8b580b7e5 100644 --- a/cmd/credential.go +++ b/cmd/credential.go @@ -1,5 +1,5 @@ /* - * Minio Cloud Storage, (C) 2015, 2016 Minio, Inc. + * Minio Cloud Storage, (C) 2015, 2016, 2017 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ package cmd import ( "crypto/rand" "encoding/base64" - "os" + "errors" "github.com/minio/mc/pkg/console" @@ -27,15 +27,20 @@ import ( ) const ( - accessKeyMinLen = 5 - accessKeyMaxLen = 20 - secretKeyMinLen = 8 - secretKeyMaxLen = 40 - - alphaNumericTable = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" - alphaNumericTableLen = byte(len(alphaNumericTable)) + accessKeyMinLen = 5 + accessKeyMaxLen = 20 + secretKeyMinLen = 8 + secretKeyMaxLenAmazon = 40 + alphaNumericTable = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" + alphaNumericTableLen = byte(len(alphaNumericTable)) ) +var ( + errInvalidAccessKeyLength = errors.New("Invalid access key, access key should be 5 to 20 characters in length") + errInvalidSecretKeyLength = errors.New("Invalid secret key, secret key should be 8 to 40 characters in length") +) +var secretKeyMaxLen = secretKeyMaxLenAmazon + func mustGetAccessKey() string { keyBytes := make([]byte, accessKeyMaxLen) if _, err := rand.Read(keyBytes); err != nil { @@ -75,65 +80,72 @@ type credential struct { secretKeyHash []byte } -// Generate a bcrypt hashed key for input secret key. -func mustGetHashedSecretKey(secretKey string) []byte { - hashedSecretKey, err := bcrypt.GenerateFromPassword([]byte(secretKey), bcrypt.DefaultCost) - if err != nil { - console.Fatalf("Unable to generate secret hash for secret key. Err: %s.\n", err) +// IsValid - returns whether credential is valid or not. +func (cred credential) IsValid() bool { + return isAccessKeyValid(cred.AccessKey) && isSecretKeyValid(cred.SecretKey) +} + +// Equals - returns whether two credentials are equal or not. +func (cred credential) Equal(ccred credential) bool { + if !ccred.IsValid() { + return false } - return hashedSecretKey + + if cred.secretKeyHash == nil { + secretKeyHash, err := bcrypt.GenerateFromPassword([]byte(cred.SecretKey), bcrypt.DefaultCost) + if err != nil { + errorIf(err, "Unable to generate hash of given password") + return false + } + + cred.secretKeyHash = secretKeyHash + } + + return (cred.AccessKey == ccred.AccessKey && + bcrypt.CompareHashAndPassword(cred.secretKeyHash, []byte(ccred.SecretKey)) == nil) +} + +func createCredential(accessKey, secretKey string) (cred credential, err error) { + if !isAccessKeyValid(accessKey) { + err = errInvalidAccessKeyLength + } else if !isSecretKeyValid(secretKey) { + err = errInvalidSecretKeyLength + } else { + var secretKeyHash []byte + secretKeyHash, err = bcrypt.GenerateFromPassword([]byte(secretKey), bcrypt.DefaultCost) + if err == nil { + cred.AccessKey = accessKey + cred.SecretKey = secretKey + cred.secretKeyHash = secretKeyHash + } + } + + return cred, err } // Initialize a new credential object -func newCredential() credential { - return newCredentialWithKeys(mustGetAccessKey(), mustGetSecretKey()) -} - -func newCredentialWithKeys(accessKey, secretKey string) credential { - secretHash := mustGetHashedSecretKey(secretKey) - return credential{accessKey, secretKey, secretHash} -} - -// Validate incoming auth keys. -func validateAuthKeys(accessKey, secretKey string) error { - // Validate the env values before proceeding. - if !isAccessKeyValid(accessKey) { - return errInvalidAccessKeyLength +func mustGetNewCredential() credential { + // Generate access key. + keyBytes := make([]byte, accessKeyMaxLen) + if _, err := rand.Read(keyBytes); err != nil { + console.Fatalln("Unable to generate access key.", err) } - if !isSecretKeyValid(secretKey) { - return errInvalidSecretKeyLength + for i := 0; i < accessKeyMaxLen; i++ { + keyBytes[i] = alphaNumericTable[keyBytes[i]%alphaNumericTableLen] } - return nil -} + accessKey := string(keyBytes) -// Variant of getCredentialFromEnv but upon error fails right here. -func mustGetCredentialFromEnv() credential { - creds, err := getCredentialFromEnv() + // Generate secret key. + keyBytes = make([]byte, secretKeyMaxLen) + if _, err := rand.Read(keyBytes); err != nil { + console.Fatalln("Unable to generate secret key.", err) + } + secretKey := string([]byte(base64.StdEncoding.EncodeToString(keyBytes))[:secretKeyMaxLen]) + + cred, err := createCredential(accessKey, secretKey) if err != nil { - console.Fatalf("Unable to load credentials from environment. Err: %s.\n", err) - } - return creds -} - -// Converts accessKey and secretKeys into credential object which -// contains bcrypt secret key hash for future validation. -func getCredentialFromEnv() (credential, error) { - // Fetch access keys from environment variables and update the config. - accessKey := os.Getenv("MINIO_ACCESS_KEY") - secretKey := os.Getenv("MINIO_SECRET_KEY") - - // Envs are set globally. - globalIsEnvCreds = accessKey != "" && secretKey != "" - - if globalIsEnvCreds { - // Validate the env values before proceeding. - if err := validateAuthKeys(accessKey, secretKey); err != nil { - return credential{}, err - } - - // Return credential object. - return newCredentialWithKeys(accessKey, secretKey), nil + console.Fatalln("Unable to generate new credential.", err) } - return credential{}, nil + return cred } diff --git a/cmd/credential_test.go b/cmd/credential_test.go new file mode 100644 index 000000000..4d30519d3 --- /dev/null +++ b/cmd/credential_test.go @@ -0,0 +1,101 @@ +/* + * Minio Cloud Storage, (C) 2017 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cmd + +import "testing" + +func TestMustGetNewCredential(t *testing.T) { + cred := mustGetNewCredential() + if !cred.IsValid() { + t.Fatalf("Failed to get new valid credential") + } +} + +func TestCreateCredential(t *testing.T) { + cred := mustGetNewCredential() + testCases := []struct { + accessKey string + secretKey string + expectedResult bool + expectedErr error + }{ + // Access key too small. + {"user", "pass", false, errInvalidAccessKeyLength}, + // Access key too long. + {"user12345678901234567", "pass", false, errInvalidAccessKeyLength}, + // Access key contains unsuppported characters. + {"!@#$", "pass", false, errInvalidAccessKeyLength}, + // Secret key too small. + {"myuser", "pass", false, errInvalidSecretKeyLength}, + // Secret key too long. + {"myuser", "pass1234567890123456789012345678901234567", false, errInvalidSecretKeyLength}, + // Success when access key contains leading/trailing spaces. + {" user ", cred.SecretKey, true, nil}, + {"myuser", "mypassword", true, nil}, + {cred.AccessKey, cred.SecretKey, true, nil}, + } + + for _, testCase := range testCases { + cred, err := createCredential(testCase.accessKey, testCase.secretKey) + if testCase.expectedErr == nil { + if err != nil { + t.Fatalf("error: expected = , got = %v", err) + } + } else if err == nil { + t.Fatalf("error: expected = %v, got = ", testCase.expectedErr) + } else if testCase.expectedErr.Error() != err.Error() { + t.Fatalf("error: expected = %v, got = %v", testCase.expectedErr, err) + } + + if testCase.expectedResult != cred.IsValid() { + t.Fatalf("cred: expected: %v, got: %v", testCase.expectedResult, cred.IsValid()) + } + } +} + +func TestCredentialEqual(t *testing.T) { + cred := mustGetNewCredential() + testCases := []struct { + cred credential + ccred credential + expectedResult bool + }{ + // Empty compare credential + {cred, credential{}, false}, + // Empty credential + {credential{}, cred, false}, + // Two different credentials + {cred, mustGetNewCredential(), false}, + // Access key is different in compare credential. + {cred, credential{AccessKey: "myuser", SecretKey: cred.SecretKey}, false}, + // Secret key is different in compare credential. + {cred, credential{AccessKey: cred.AccessKey, SecretKey: "mypassword"}, false}, + // secretHashKey is missing in compare credential. + {cred, credential{AccessKey: cred.AccessKey, SecretKey: cred.SecretKey}, true}, + // secretHashKey is missing in credential. + {credential{AccessKey: cred.AccessKey, SecretKey: cred.SecretKey}, cred, true}, + // Same credentials. + {cred, cred, true}, + } + + for _, testCase := range testCases { + result := testCase.cred.Equal(testCase.ccred) + if result != testCase.expectedResult { + t.Fatalf("cred: expected: %v, got: %v", testCase.expectedResult, result) + } + } +} diff --git a/cmd/erasure-createfile.go b/cmd/erasure-createfile.go index f95c0c18c..807684210 100644 --- a/cmd/erasure-createfile.go +++ b/cmd/erasure-createfile.go @@ -114,6 +114,7 @@ func appendFile(disks []StorageAPI, volume, path string, enBlocks [][]byte, hash // Write encoded data to quorum disks in parallel. for index, disk := range disks { if disk == nil { + wErrs[index] = traceError(errDiskNotFound) continue } wg.Add(1) @@ -123,6 +124,8 @@ func appendFile(disks []StorageAPI, volume, path string, enBlocks [][]byte, hash wErr := disk.AppendFile(volume, path, enBlocks[index]) if wErr != nil { wErrs[index] = traceError(wErr) + // Ignore disk which returned an error. + disks[index] = nil return } diff --git a/cmd/erasure-readfile.go b/cmd/erasure-readfile.go index 6d50d933a..682522c36 100644 --- a/cmd/erasure-readfile.go +++ b/cmd/erasure-readfile.go @@ -192,7 +192,7 @@ func erasureReadFile(writer io.Writer, disks []StorageAPI, volume string, path s }() // Total bytes written to writer - bytesWritten := int64(0) + var bytesWritten int64 startBlock := offset / blockSize endBlock := (offset + length) / blockSize @@ -263,7 +263,7 @@ func erasureReadFile(writer io.Writer, disks []StorageAPI, volume string, path s } // Offset in enBlocks from where data should be read from. - enBlocksOffset := int64(0) + var enBlocksOffset int64 // Total data to be read from enBlocks. enBlocksLength := curBlockSize diff --git a/cmd/erasure-readfile_test.go b/cmd/erasure-readfile_test.go index 51071952f..4fa3c43fa 100644 --- a/cmd/erasure-readfile_test.go +++ b/cmd/erasure-readfile_test.go @@ -121,35 +121,6 @@ func testGetReadDisks(t *testing.T, xl *xlObjects) { } } -// Test getOrderedDisks which returns ordered slice of disks from their -// actual distribution. -func testGetOrderedDisks(t *testing.T, xl *xlObjects) { - disks := xl.storageDisks - distribution := []int{16, 14, 12, 10, 8, 6, 4, 2, 1, 3, 5, 7, 9, 11, 13, 15} - orderedDisks := getOrderedDisks(distribution, disks) - // From the "distribution" above you can notice that: - // 1st data block is in the 9th disk (i.e distribution index 8) - // 2nd data block is in the 8th disk (i.e distribution index 7) and so on. - if orderedDisks[0] != disks[8] || - orderedDisks[1] != disks[7] || - orderedDisks[2] != disks[9] || - orderedDisks[3] != disks[6] || - orderedDisks[4] != disks[10] || - orderedDisks[5] != disks[5] || - orderedDisks[6] != disks[11] || - orderedDisks[7] != disks[4] || - orderedDisks[8] != disks[12] || - orderedDisks[9] != disks[3] || - orderedDisks[10] != disks[13] || - orderedDisks[11] != disks[2] || - orderedDisks[12] != disks[14] || - orderedDisks[13] != disks[1] || - orderedDisks[14] != disks[15] || - orderedDisks[15] != disks[0] { - t.Errorf("getOrderedDisks returned incorrect order.") - } -} - // Test for isSuccessDataBlocks and isSuccessDecodeBlocks. func TestIsSuccessBlocks(t *testing.T) { dataBlocks := 8 @@ -217,7 +188,7 @@ func TestIsSuccessBlocks(t *testing.T) { } } -// Wrapper function for testGetReadDisks, testGetOrderedDisks. +// Wrapper function for testGetReadDisks, testShuffleDisks. func TestErasureReadUtils(t *testing.T) { nDisks := 16 disks, err := getRandomDisks(nDisks) @@ -236,7 +207,6 @@ func TestErasureReadUtils(t *testing.T) { defer removeRoots(disks) xl := objLayer.(*xlObjects) testGetReadDisks(t, xl) - testGetOrderedDisks(t, xl) } // Simulates a faulty disk for ReadFile() diff --git a/cmd/erasure-utils.go b/cmd/erasure-utils.go index 814df1c47..6fe01c499 100644 --- a/cmd/erasure-utils.go +++ b/cmd/erasure-utils.go @@ -116,7 +116,7 @@ func writeDataBlocks(dst io.Writer, enBlocks [][]byte, dataBlocks int, offset in write := length // Counter to increment total written. - totalWritten := int64(0) + var totalWritten int64 // Write all data blocks to dst. for _, block := range enBlocks[:dataBlocks] { @@ -180,7 +180,7 @@ func copyBuffer(writer io.Writer, disk StorageAPI, volume string, path string, b } // Starting offset for Reading the file. - startOffset := int64(0) + var startOffset int64 // Read until io.EOF. for { diff --git a/cmd/errors.go b/cmd/errors.go index b61685af8..0476a3231 100644 --- a/cmd/errors.go +++ b/cmd/errors.go @@ -86,10 +86,10 @@ func traceError(e error, errs ...error) error { fn := runtime.FuncForPC(pc) file, line := fn.FileLine(pc) name := fn.Name() - if strings.HasSuffix(name, "ServeHTTP") { + if hasSuffix(name, "ServeHTTP") { break } - if strings.HasSuffix(name, "runtime.") { + if hasSuffix(name, "runtime.") { break } diff --git a/cmd/event-notifier.go b/cmd/event-notifier.go index 9db12cccf..f97cac51c 100644 --- a/cmd/event-notifier.go +++ b/cmd/event-notifier.go @@ -313,6 +313,9 @@ func eventNotifyForBucketListeners(eventType, objectName, bucketName string, // eventNotify notifies an event to relevant targets based on their // bucket configuration (notifications and listeners). func eventNotify(event eventData) { + if globalEventNotifier == nil { + return + } // Notifies a new event. // List of events reported through this function are // - s3:ObjectCreated:Put @@ -537,6 +540,28 @@ func loadAllBucketNotifications(objAPI ObjectLayer) (map[string]*notificationCon return nConfigs, lConfigs, nil } +// addQueueTarget - calls newTargetFunc function and adds its returned value to queueTargets +func addQueueTarget(queueTargets map[string]*logrus.Logger, + accountID, queueType string, + newTargetFunc func(string) (*logrus.Logger, error)) (string, error) { + + // Construct the queue ARN for AMQP. + queueARN := minioSqs + serverConfig.GetRegion() + ":" + accountID + ":" + queueType + + // Queue target if already initialized we move to the next ARN. + if _, ok := queueTargets[queueARN]; ok { + return queueARN, nil + } + + // Using accountID we can now initialize a new AMQP logrus instance. + logger, err := newTargetFunc(accountID) + if err == nil { + queueTargets[queueARN] = logger + } + + return queueARN, err +} + // Loads all queue targets, initializes each queueARNs depending on their config. // Each instance of queueARN registers its own logrus to communicate with the // queue service. QueueARN once initialized is not initialized again for the @@ -548,54 +573,37 @@ func loadAllQueueTargets() (map[string]*logrus.Logger, error) { if !amqpN.Enable { continue } - // Construct the queue ARN for AMQP. - queueARN := minioSqs + serverConfig.GetRegion() + ":" + accountID + ":" + queueTypeAMQP - // Queue target if already initialized we move to the next ARN. - _, ok := queueTargets[queueARN] - if ok { - continue - } - // Using accountID we can now initialize a new AMQP logrus instance. - amqpLog, err := newAMQPNotify(accountID) - if err != nil { - // Encapsulate network error to be more informative. + + if queueARN, err := addQueueTarget(queueTargets, accountID, queueTypeAMQP, newAMQPNotify); err != nil { if _, ok := err.(net.Error); ok { - return nil, &net.OpError{ + err = &net.OpError{ Op: "Connecting to " + queueARN, Net: "tcp", Err: err, } } + return nil, err } - queueTargets[queueARN] = amqpLog } + // Load all nats targets, initialize their respective loggers. for accountID, natsN := range serverConfig.Notify.GetNATS() { if !natsN.Enable { continue } - // Construct the queue ARN for NATS. - queueARN := minioSqs + serverConfig.GetRegion() + ":" + accountID + ":" + queueTypeNATS - // Queue target if already initialized we move to the next ARN. - _, ok := queueTargets[queueARN] - if ok { - continue - } - // Using accountID we can now initialize a new NATS logrus instance. - natsLog, err := newNATSNotify(accountID) - if err != nil { - // Encapsulate network error to be more informative. + + if queueARN, err := addQueueTarget(queueTargets, accountID, queueTypeNATS, newNATSNotify); err != nil { if _, ok := err.(net.Error); ok { - return nil, &net.OpError{ + err = &net.OpError{ Op: "Connecting to " + queueARN, Net: "tcp", Err: err, } } + return nil, err } - queueTargets[queueARN] = natsLog } // Load redis targets, initialize their respective loggers. @@ -603,27 +611,18 @@ func loadAllQueueTargets() (map[string]*logrus.Logger, error) { if !redisN.Enable { continue } - // Construct the queue ARN for Redis. - queueARN := minioSqs + serverConfig.GetRegion() + ":" + accountID + ":" + queueTypeRedis - // Queue target if already initialized we move to the next ARN. - _, ok := queueTargets[queueARN] - if ok { - continue - } - // Using accountID we can now initialize a new Redis logrus instance. - redisLog, err := newRedisNotify(accountID) - if err != nil { - // Encapsulate network error to be more informative. + + if queueARN, err := addQueueTarget(queueTargets, accountID, queueTypeRedis, newRedisNotify); err != nil { if _, ok := err.(net.Error); ok { - return nil, &net.OpError{ + err = &net.OpError{ Op: "Connecting to " + queueARN, Net: "tcp", Err: err, } } + return nil, err } - queueTargets[queueARN] = redisLog } // Load Webhook targets, initialize their respective loggers. @@ -631,20 +630,10 @@ func loadAllQueueTargets() (map[string]*logrus.Logger, error) { if !webhookN.Enable { continue } - // Construct the queue ARN for Webhook. - queueARN := minioSqs + serverConfig.GetRegion() + ":" + accountID + ":" + queueTypeWebhook - _, ok := queueTargets[queueARN] - if ok { - continue - } - - // Using accountID we can now initialize a new Webhook logrus instance. - webhookLog, err := newWebhookNotify(accountID) - if err != nil { + if _, err := addQueueTarget(queueTargets, accountID, queueTypeWebhook, newWebhookNotify); err != nil { return nil, err } - queueTargets[queueARN] = webhookLog } // Load elastic targets, initialize their respective loggers. @@ -652,25 +641,18 @@ func loadAllQueueTargets() (map[string]*logrus.Logger, error) { if !elasticN.Enable { continue } - // Construct the queue ARN for Elastic. - queueARN := minioSqs + serverConfig.GetRegion() + ":" + accountID + ":" + queueTypeElastic - _, ok := queueTargets[queueARN] - if ok { - continue - } - // Using accountID we can now initialize a new ElasticSearch logrus instance. - elasticLog, err := newElasticNotify(accountID) - if err != nil { - // Encapsulate network error to be more informative. + + if queueARN, err := addQueueTarget(queueTargets, accountID, queueTypeElastic, newElasticNotify); err != nil { if _, ok := err.(net.Error); ok { - return nil, &net.OpError{ - Op: "Connecting to " + queueARN, Net: "tcp", + err = &net.OpError{ + Op: "Connecting to " + queueARN, + Net: "tcp", Err: err, } } + return nil, err } - queueTargets[queueARN] = elasticLog } // Load PostgreSQL targets, initialize their respective loggers. @@ -678,50 +660,37 @@ func loadAllQueueTargets() (map[string]*logrus.Logger, error) { if !pgN.Enable { continue } - // Construct the queue ARN for Postgres. - queueARN := minioSqs + serverConfig.GetRegion() + ":" + accountID + ":" + queueTypePostgreSQL - _, ok := queueTargets[queueARN] - if ok { - continue - } - // Using accountID initialize a new Postgresql logrus instance. - pgLog, err := newPostgreSQLNotify(accountID) - if err != nil { - // Encapsulate network error to be more informative. + + if queueARN, err := addQueueTarget(queueTargets, accountID, queueTypePostgreSQL, newPostgreSQLNotify); err != nil { if _, ok := err.(net.Error); ok { - return nil, &net.OpError{ - Op: "Connecting to " + queueARN, Net: "tcp", + err = &net.OpError{ + Op: "Connecting to " + queueARN, + Net: "tcp", Err: err, } } + return nil, err } - queueTargets[queueARN] = pgLog } + // Load Kafka targets, initialize their respective loggers. for accountID, kafkaN := range serverConfig.Notify.GetKafka() { if !kafkaN.Enable { continue } - // Construct the queue ARN for Kafka. - queueARN := minioSqs + serverConfig.GetRegion() + ":" + accountID + ":" + queueTypeKafka - _, ok := queueTargets[queueARN] - if ok { - continue - } - // Using accountID initialize a new Kafka logrus instance. - kafkaLog, err := newKafkaNotify(accountID) - if err != nil { - // Encapsulate network error to be more informative. + + if queueARN, err := addQueueTarget(queueTargets, accountID, queueTypeKafka, newKafkaNotify); err != nil { if _, ok := err.(net.Error); ok { - return nil, &net.OpError{ - Op: "Connecting to " + queueARN, Net: "tcp", + err = &net.OpError{ + Op: "Connecting to " + queueARN, + Net: "tcp", Err: err, } } + return nil, err } - queueTargets[queueARN] = kafkaLog } // Successfully initialized queue targets. diff --git a/cmd/format-config-v1_test.go b/cmd/format-config-v1_test.go index b92532b0a..b96a83ca0 100644 --- a/cmd/format-config-v1_test.go +++ b/cmd/format-config-v1_test.go @@ -213,9 +213,7 @@ func genFormatXLInvalidDisksOrder() []*formatConfigV1 { } // Re order jbod for failure case. var jbod1 = make([]string, 8) - for i, j := range jbod { - jbod1[i] = j - } + copy(jbod1, jbod) jbod1[1], jbod1[2] = jbod[2], jbod[1] formatConfigs[2].XL.JBOD = jbod1 return formatConfigs @@ -576,9 +574,7 @@ func TestSavedUUIDOrder(t *testing.T) { } // Re order jbod for failure case. var jbod1 = make([]string, 8) - for i, j := range jbod { - jbod1[i] = j - } + copy(jbod1, jbod) jbod1[1], jbod1[2] = jbod[2], jbod[1] formatConfigs[2].XL.JBOD = jbod1 uuidTestCases[1].shouldPass = false diff --git a/cmd/fs-v1-background-append.go b/cmd/fs-v1-background-append.go index 2e4362b4c..c3b7c868a 100644 --- a/cmd/fs-v1-background-append.go +++ b/cmd/fs-v1-background-append.go @@ -210,7 +210,7 @@ func (fs fsObjects) appendParts(bucket, object, uploadID string, info bgAppendPa func (fs fsObjects) appendPart(bucket, object, uploadID string, part objectPartInfo, buf []byte) error { partPath := pathJoin(fs.fsPath, minioMetaMultipartBucket, bucket, object, uploadID, part.Name) - offset := int64(0) + var offset int64 // Read each file part to start writing to the temporary concatenated object. file, size, err := fsOpenFile(partPath, offset) if err != nil { diff --git a/cmd/fs-v1-helpers.go b/cmd/fs-v1-helpers.go index a64ad4c71..85fcce0c1 100644 --- a/cmd/fs-v1-helpers.go +++ b/cmd/fs-v1-helpers.go @@ -228,20 +228,24 @@ func fsOpenFile(readPath string, offset int64) (io.ReadCloser, int64, error) { } // Creates a file and copies data from incoming reader. Staging buffer is used by io.CopyBuffer. -func fsCreateFile(tempObjPath string, reader io.Reader, buf []byte, fallocSize int64) (int64, error) { - if tempObjPath == "" || reader == nil || buf == nil { +func fsCreateFile(filePath string, reader io.Reader, buf []byte, fallocSize int64) (int64, error) { + if filePath == "" || reader == nil || buf == nil { return 0, traceError(errInvalidArgument) } - if err := checkPathLength(tempObjPath); err != nil { + if err := checkPathLength(filePath); err != nil { return 0, traceError(err) } - if err := mkdirAll(pathutil.Dir(tempObjPath), 0777); err != nil { + if err := mkdirAll(pathutil.Dir(filePath), 0777); err != nil { return 0, traceError(err) } - writer, err := os.OpenFile(preparePath(tempObjPath), os.O_CREATE|os.O_WRONLY, 0666) + if err := checkDiskFree(pathutil.Dir(filePath), fallocSize); err != nil { + return 0, traceError(err) + } + + writer, err := os.OpenFile(preparePath(filePath), os.O_CREATE|os.O_WRONLY, 0666) if err != nil { // File path cannot be verified since one of the parents is a file. if isSysErrNotDir(err) { diff --git a/cmd/fs-v1-metadata.go b/cmd/fs-v1-metadata.go index f467991db..3f7f1949a 100644 --- a/cmd/fs-v1-metadata.go +++ b/cmd/fs-v1-metadata.go @@ -238,12 +238,9 @@ func saveFormatFS(formatPath string, fsFormat *formatConfigV1) error { } defer lk.Close() - if _, err = lk.Write(metadataBytes); err != nil { - return err - } - + _, err = lk.Write(metadataBytes) // Success. - return nil + return err } // Return if the part info in uploadedParts and completeParts are same. diff --git a/cmd/fs-v1-multipart.go b/cmd/fs-v1-multipart.go index 4ae57635c..69ff09e1c 100644 --- a/cmd/fs-v1-multipart.go +++ b/cmd/fs-v1-multipart.go @@ -278,7 +278,7 @@ func (fs fsObjects) listMultipartUploads(bucket, prefix, keyMarker, uploadIDMark } entry := strings.TrimPrefix(walkResult.entry, retainSlash(bucket)) - if strings.HasSuffix(walkResult.entry, slashSeparator) { + if hasSuffix(walkResult.entry, slashSeparator) { uploads = append(uploads, uploadMetadata{ Object: entry, }) @@ -314,7 +314,7 @@ func (fs fsObjects) listMultipartUploads(bucket, prefix, keyMarker, uploadIDMark for _, upload := range uploads { var objectName string var uploadID string - if strings.HasSuffix(upload.Object, slashSeparator) { + if hasSuffix(upload.Object, slashSeparator) { // All directory entries are common prefixes. uploadID = "" // Upload ids are empty for CommonPrefixes. objectName = upload.Object @@ -463,7 +463,6 @@ func (fs fsObjects) CopyObjectPart(srcBucket, srcObject, dstBucket, dstObject, u pipeReader, pipeWriter := io.Pipe() go func() { - startOffset := int64(0) // Read the whole file. if gerr := fs.GetObject(srcBucket, srcObject, startOffset, length, pipeWriter); gerr != nil { errorIf(gerr, "Unable to read %s/%s.", srcBucket, srcObject) pipeWriter.CloseWithError(gerr) @@ -864,7 +863,7 @@ func (fs fsObjects) CompleteMultipartUpload(bucket string, object string, upload multipartPartFile := pathJoin(fs.fsPath, minioMetaMultipartBucket, uploadIDPath, partSuffix) var reader io.ReadCloser - offset := int64(0) + var offset int64 reader, _, err = fsOpenFile(multipartPartFile, offset) if err != nil { fs.rwPool.Close(fsMetaPathMultipart) diff --git a/cmd/fs-v1-rwpool.go b/cmd/fs-v1-rwpool.go index b568c05d5..74a0f38b9 100644 --- a/cmd/fs-v1-rwpool.go +++ b/cmd/fs-v1-rwpool.go @@ -50,7 +50,6 @@ func (fsi *fsIOPool) Open(path string) (*lock.RLockedFile, error) { fsi.Lock() rlkFile, ok := fsi.readersMap[path] - // File reference exists on map, validate if its // really closed and we are safe to purge it. if ok && rlkFile != nil { @@ -76,8 +75,9 @@ func (fsi *fsIOPool) Open(path string) (*lock.RLockedFile, error) { // read lock mode. if !ok { var err error + var newRlkFile *lock.RLockedFile // Open file for reading. - rlkFile, err = lock.RLockedOpenFile(preparePath(path)) + newRlkFile, err = lock.RLockedOpenFile(preparePath(path)) if err != nil { if os.IsNotExist(err) { return nil, errFileNotFound @@ -95,6 +95,30 @@ func (fsi *fsIOPool) Open(path string) (*lock.RLockedFile, error) { // Save new reader on the map. fsi.Lock() + rlkFile, ok = fsi.readersMap[path] + if ok && rlkFile != nil { + // If the file is closed and not removed from map is a bug. + if rlkFile.IsClosed() { + // Log this as an error. + errorIf(errUnexpected, "Unexpected entry found on the map %s", path) + + // Purge the cached lock path from map. + delete(fsi.readersMap, path) + + // Save the newly acquired read locked file. + rlkFile = newRlkFile + } else { + // Increment the lock ref, since the file is not closed yet + // and caller requested to read the file again. + rlkFile.IncLockRef() + newRlkFile.Close() + } + } else { + // Save the newly acquired read locked file. + rlkFile = newRlkFile + } + + // Save the rlkFile back on the map. fsi.readersMap[path] = rlkFile fsi.Unlock() } diff --git a/cmd/fs-v1.go b/cmd/fs-v1.go index 16f3263a3..b9d59a1ac 100644 --- a/cmd/fs-v1.go +++ b/cmd/fs-v1.go @@ -19,18 +19,14 @@ package cmd import ( "crypto/md5" "encoding/hex" - "errors" "fmt" "hash" "io" "os" "path/filepath" - "runtime" "sort" - "strings" "syscall" - "github.com/minio/minio/pkg/disk" "github.com/minio/minio/pkg/lock" "github.com/minio/sha256-simd" ) @@ -44,9 +40,6 @@ type fsObjects struct { // temporary transactions. fsUUID string - minFreeSpace int64 - minFreeInodes int64 - // FS rw pool. rwPool *fsIOPool @@ -74,12 +67,7 @@ func initMetaVolumeFS(fsPath, fsUUID string) error { } metaMultipartPath := pathJoin(fsPath, minioMetaMultipartBucket) - if err := mkdirAll(metaMultipartPath, 0777); err != nil { - return err - } - - // Return success here. - return nil + return mkdirAll(metaMultipartPath, 0777) } @@ -141,10 +129,8 @@ func newFSObjectLayer(fsPath string) (ObjectLayer, error) { // Initialize fs objects. fs := &fsObjects{ - fsPath: fsPath, - fsUUID: fsUUID, - minFreeSpace: fsMinFreeSpace, - minFreeInodes: fsMinFreeInodes, + fsPath: fsPath, + fsUUID: fsUUID, rwPool: &fsIOPool{ readersMap: make(map[string]*lock.RLockedFile), }, @@ -170,41 +156,6 @@ func newFSObjectLayer(fsPath string) (ObjectLayer, error) { return fs, nil } -// checkDiskFree verifies if disk path has sufficient minimum free disk space and files. -func (fs fsObjects) checkDiskFree() (err error) { - // We don't validate disk space or inode utilization on windows. - // Each windows calls to 'GetVolumeInformationW' takes around 3-5seconds. - if runtime.GOOS == globalWindowsOSName { - return nil - } - - var di disk.Info - di, err = getDiskInfo(preparePath(fs.fsPath)) - if err != nil { - return err - } - - // Remove 5% from free space for cumulative disk space used for journalling, inodes etc. - availableDiskSpace := float64(di.Free) * 0.95 - if int64(availableDiskSpace) <= fs.minFreeSpace { - return errDiskFull - } - - // Some filesystems do not implement a way to provide total inodes available, instead inodes - // are allocated based on available disk space. For example CephFS, StoreNext CVFS, AzureFile driver. - // Allow for the available disk to be separately validate and we will validate inodes only if - // total inodes are provided by the underlying filesystem. - if di.Files != 0 && di.FSType != "NFS" { - availableFiles := int64(di.Ffree) - if availableFiles <= fs.minFreeInodes { - return errDiskFull - } - } - - // Success. - return nil -} - // Should be called when process shuts down. func (fs fsObjects) Shutdown() error { // Cleanup and delete tmp uuid. @@ -291,12 +242,11 @@ func (fs fsObjects) ListBuckets() ([]BucketInfo, error) { return nil, toObjectErr(traceError(errDiskNotFound)) } - var invalidBucketNames []string for _, entry := range entries { - if entry == minioMetaBucket+"/" || !strings.HasSuffix(entry, slashSeparator) { + // Ignore all reserved bucket names and invalid bucket names. + if isReservedOrInvalidBucket(entry) { continue } - var fi os.FileInfo fi, err = fsStatDir(pathJoin(fs.fsPath, entry)) if err != nil { @@ -310,24 +260,13 @@ func (fs fsObjects) ListBuckets() ([]BucketInfo, error) { return nil, err } - if !IsValidBucketName(fi.Name()) { - invalidBucketNames = append(invalidBucketNames, fi.Name()) - continue - } - bucketInfos = append(bucketInfos, BucketInfo{ Name: fi.Name(), - // As os.Stat() doesn't carry other than ModTime(), use ModTime() as CreatedTime. + // As os.Stat() doesnt carry CreatedTime, use ModTime() as CreatedTime. Created: fi.ModTime(), }) } - // Print a user friendly message if we indeed skipped certain directories which are - // incompatible with S3's bucket name restrictions. - if len(invalidBucketNames) > 0 { - errorIf(errors.New("One or more invalid bucket names found"), "Skipping %s", invalidBucketNames) - } - // Sort bucket infos by bucket name. sort.Sort(byBucketName(bucketInfos)) @@ -380,7 +319,7 @@ func (fs fsObjects) CopyObject(srcBucket, srcObject, dstBucket, dstObject string } // Check if this request is only metadata update. - cpMetadataOnly := strings.EqualFold(pathJoin(srcBucket, srcObject), pathJoin(dstBucket, dstObject)) + cpMetadataOnly := isStringEqual(pathJoin(srcBucket, srcObject), pathJoin(dstBucket, dstObject)) if cpMetadataOnly { fsMetaPath := pathJoin(fs.fsPath, minioMetaBucket, bucketMetaPrefix, srcBucket, srcObject, fsMetaJSONFile) var wlk *lock.LockedFile @@ -409,7 +348,7 @@ func (fs fsObjects) CopyObject(srcBucket, srcObject, dstBucket, dstObject string pipeReader, pipeWriter := io.Pipe() go func() { - startOffset := int64(0) // Read the whole file. + var startOffset int64 // Read the whole file. if gerr := fs.GetObject(srcBucket, srcObject, startOffset, length, pipeWriter); gerr != nil { errorIf(gerr, "Unable to read %s/%s.", srcBucket, srcObject) pipeWriter.CloseWithError(gerr) @@ -531,6 +470,12 @@ func (fs fsObjects) getObjectInfo(bucket, object string) (ObjectInfo, error) { // GetObjectInfo - reads object metadata and replies back ObjectInfo. func (fs fsObjects) GetObjectInfo(bucket, object string) (ObjectInfo, error) { + // This is a special case with object whose name ends with + // a slash separator, we always return object not found here. + if hasSuffix(object, slashSeparator) { + return ObjectInfo{}, toObjectErr(traceError(errFileNotFound), bucket, object) + } + if err := checkGetObjArgs(bucket, object); err != nil { return ObjectInfo{}, err } @@ -780,7 +725,7 @@ func (fs fsObjects) ListObjects(bucket, prefix, marker, delimiter string, maxKey // Convert entry to ObjectInfo entryToObjectInfo := func(entry string) (objInfo ObjectInfo, err error) { - if strings.HasSuffix(entry, slashSeparator) { + if hasSuffix(entry, slashSeparator) { // Object name needs to be full path. objInfo.Name = entry objInfo.IsDir = true @@ -804,7 +749,7 @@ func (fs fsObjects) ListObjects(bucket, prefix, marker, delimiter string, maxKey // bucket argument is unused as we don't need to StatFile // to figure if it's a file, just need to check that the // object string does not end with "/". - return !strings.HasSuffix(object, slashSeparator) + return !hasSuffix(object, slashSeparator) } listDir := fs.listDirFactory(isLeaf) walkResultCh = startTreeWalk(bucket, prefix, marker, recursive, listDir, isLeaf, endWalkCh) @@ -882,3 +827,8 @@ func (fs fsObjects) ListObjectsHeal(bucket, prefix, marker, delimiter string, ma func (fs fsObjects) ListBucketsHeal() ([]BucketInfo, error) { return []BucketInfo{}, traceError(NotImplemented{}) } + +func (fs fsObjects) ListUploadsHeal(bucket, prefix, marker, uploadIDMarker, + delimiter string, maxUploads int) (ListMultipartsInfo, error) { + return ListMultipartsInfo{}, traceError(NotImplemented{}) +} diff --git a/cmd/gateway-handlers.go b/cmd/gateway-handlers.go new file mode 100644 index 000000000..31a5ea06e --- /dev/null +++ b/cmd/gateway-handlers.go @@ -0,0 +1,703 @@ +/* + * Minio Cloud Storage, (C) 2017 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cmd + +import ( + "bytes" + "io" + "io/ioutil" + "net/http" + + "encoding/json" + "encoding/xml" + + router "github.com/gorilla/mux" + "github.com/minio/minio-go/pkg/policy" +) + +// GetObjectHandler - GET Object +// ---------- +// This implementation of the GET operation retrieves object. To use GET, +// you must have READ access to the object. +func (api gatewayAPIHandlers) GetObjectHandler(w http.ResponseWriter, r *http.Request) { + var object, bucket string + vars := router.Vars(r) + bucket = vars["bucket"] + object = vars["object"] + + // Fetch object stat info. + objectAPI := api.ObjectAPI() + if objectAPI == nil { + writeErrorResponse(w, ErrServerNotInitialized, r.URL) + return + } + + reqAuthType := getRequestAuthType(r) + + switch reqAuthType { + case authTypePresignedV2, authTypeSignedV2: + // Signature V2 validation. + s3Error := isReqAuthenticatedV2(r) + if s3Error != ErrNone { + errorIf(errSignatureMismatch, dumpRequest(r)) + writeErrorResponse(w, s3Error, r.URL) + return + } + case authTypeSigned, authTypePresigned: + s3Error := isReqAuthenticated(r, serverConfig.GetRegion()) + if s3Error != ErrNone { + errorIf(errSignatureMismatch, dumpRequest(r)) + writeErrorResponse(w, s3Error, r.URL) + return + } + } + + getObjectInfo := objectAPI.GetObjectInfo + if reqAuthType == authTypeAnonymous { + getObjectInfo = objectAPI.AnonGetObjectInfo + } + objInfo, err := getObjectInfo(bucket, object) + if err != nil { + errorIf(err, "Unable to fetch object info.") + apiErr := toAPIErrorCode(err) + if apiErr == ErrNoSuchKey { + apiErr = errAllowableObjectNotFound(bucket, r) + } + writeErrorResponse(w, apiErr, r.URL) + return + } + + // Get request range. + var hrange *httpRange + rangeHeader := r.Header.Get("Range") + if rangeHeader != "" { + if hrange, err = parseRequestRange(rangeHeader, objInfo.Size); err != nil { + // Handle only errInvalidRange + // Ignore other parse error and treat it as regular Get request like Amazon S3. + if err == errInvalidRange { + writeErrorResponse(w, ErrInvalidRange, r.URL) + return + } + + // log the error. + errorIf(err, "Invalid request range") + } + } + + // Validate pre-conditions if any. + if checkPreconditions(w, r, objInfo) { + return + } + + // Get the object. + var startOffset int64 + length := objInfo.Size + if hrange != nil { + startOffset = hrange.offsetBegin + length = hrange.getLength() + } + // Indicates if any data was written to the http.ResponseWriter + dataWritten := false + // io.Writer type which keeps track if any data was written. + writer := funcToWriter(func(p []byte) (int, error) { + if !dataWritten { + // Set headers on the first write. + // Set standard object headers. + setObjectHeaders(w, objInfo, hrange) + + // Set any additional requested response headers. + setGetRespHeaders(w, r.URL.Query()) + + dataWritten = true + } + return w.Write(p) + }) + + getObject := objectAPI.GetObject + if reqAuthType == authTypeAnonymous { + getObject = objectAPI.AnonGetObject + } + + // Reads the object at startOffset and writes to mw. + if err := getObject(bucket, object, startOffset, length, writer); err != nil { + errorIf(err, "Unable to write to client.") + if !dataWritten { + // Error response only if no data has been written to client yet. i.e if + // partial data has already been written before an error + // occurred then no point in setting StatusCode and + // sending error XML. + writeErrorResponse(w, toAPIErrorCode(err), r.URL) + } + return + } + if !dataWritten { + // If ObjectAPI.GetObject did not return error and no data has + // been written it would mean that it is a 0-byte object. + // call wrter.Write(nil) to set appropriate headers. + writer.Write(nil) + } +} + +// HeadObjectHandler - HEAD Object +// ----------- +// The HEAD operation retrieves metadata from an object without returning the object itself. +func (api gatewayAPIHandlers) HeadObjectHandler(w http.ResponseWriter, r *http.Request) { + var object, bucket string + vars := router.Vars(r) + bucket = vars["bucket"] + object = vars["object"] + + objectAPI := api.ObjectAPI() + if objectAPI == nil { + writeErrorResponseHeadersOnly(w, ErrServerNotInitialized) + return + } + + reqAuthType := getRequestAuthType(r) + + switch reqAuthType { + case authTypePresignedV2, authTypeSignedV2: + // Signature V2 validation. + s3Error := isReqAuthenticatedV2(r) + if s3Error != ErrNone { + errorIf(errSignatureMismatch, dumpRequest(r)) + writeErrorResponse(w, s3Error, r.URL) + return + } + case authTypeSigned, authTypePresigned: + s3Error := isReqAuthenticated(r, serverConfig.GetRegion()) + if s3Error != ErrNone { + errorIf(errSignatureMismatch, dumpRequest(r)) + writeErrorResponse(w, s3Error, r.URL) + return + } + } + + getObjectInfo := objectAPI.GetObjectInfo + if reqAuthType == authTypeAnonymous { + getObjectInfo = objectAPI.AnonGetObjectInfo + } + objInfo, err := getObjectInfo(bucket, object) + if err != nil { + errorIf(err, "Unable to fetch object info.") + apiErr := toAPIErrorCode(err) + if apiErr == ErrNoSuchKey { + apiErr = errAllowableObjectNotFound(bucket, r) + } + writeErrorResponse(w, apiErr, r.URL) + return + } + + // Validate pre-conditions if any. + if checkPreconditions(w, r, objInfo) { + return + } + + // Set standard object headers. + setObjectHeaders(w, objInfo, nil) + + // Successful response. + w.WriteHeader(http.StatusOK) +} + +// DeleteMultipleObjectsHandler - deletes multiple objects. +func (api gatewayAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter, r *http.Request) { + vars := router.Vars(r) + bucket := vars["bucket"] + + objectAPI := api.ObjectAPI() + if objectAPI == nil { + writeErrorResponse(w, ErrServerNotInitialized, r.URL) + return + } + + if s3Error := checkRequestAuthType(r, bucket, "s3:DeleteObject", serverConfig.GetRegion()); s3Error != ErrNone { + writeErrorResponse(w, s3Error, r.URL) + return + } + + // Content-Length is required and should be non-zero + // http://docs.aws.amazon.com/AmazonS3/latest/API/multiobjectdeleteapi.html + if r.ContentLength <= 0 { + writeErrorResponse(w, ErrMissingContentLength, r.URL) + return + } + + // Content-Md5 is requied should be set + // http://docs.aws.amazon.com/AmazonS3/latest/API/multiobjectdeleteapi.html + if _, ok := r.Header["Content-Md5"]; !ok { + writeErrorResponse(w, ErrMissingContentMD5, r.URL) + return + } + + // Allocate incoming content length bytes. + deleteXMLBytes := make([]byte, r.ContentLength) + + // Read incoming body XML bytes. + if _, err := io.ReadFull(r.Body, deleteXMLBytes); err != nil { + errorIf(err, "Unable to read HTTP body.") + writeErrorResponse(w, ErrInternalError, r.URL) + return + } + + // Unmarshal list of keys to be deleted. + deleteObjects := &DeleteObjectsRequest{} + if err := xml.Unmarshal(deleteXMLBytes, deleteObjects); err != nil { + errorIf(err, "Unable to unmarshal delete objects request XML.") + writeErrorResponse(w, ErrMalformedXML, r.URL) + return + } + + var dErrs = make([]error, len(deleteObjects.Objects)) + + // Delete all requested objects in parallel. + for index, object := range deleteObjects.Objects { + dErr := objectAPI.DeleteObject(bucket, object.ObjectName) + if dErr != nil { + dErrs[index] = dErr + } + } + + // Collect deleted objects and errors if any. + var deletedObjects []ObjectIdentifier + var deleteErrors []DeleteError + for index, err := range dErrs { + object := deleteObjects.Objects[index] + // Success deleted objects are collected separately. + if err == nil { + deletedObjects = append(deletedObjects, object) + continue + } + if _, ok := errorCause(err).(ObjectNotFound); ok { + // If the object is not found it should be + // accounted as deleted as per S3 spec. + deletedObjects = append(deletedObjects, object) + continue + } + errorIf(err, "Unable to delete object. %s", object.ObjectName) + // Error during delete should be collected separately. + deleteErrors = append(deleteErrors, DeleteError{ + Code: errorCodeResponse[toAPIErrorCode(err)].Code, + Message: errorCodeResponse[toAPIErrorCode(err)].Description, + Key: object.ObjectName, + }) + } + + // Generate response + response := generateMultiDeleteResponse(deleteObjects.Quiet, deletedObjects, deleteErrors) + encodedSuccessResponse := encodeResponse(response) + + // Write success response. + writeSuccessResponseXML(w, encodedSuccessResponse) +} + +// PutBucketPolicyHandler - PUT Bucket policy +// ----------------- +// This implementation of the PUT operation uses the policy +// subresource to add to or replace a policy on a bucket +func (api gatewayAPIHandlers) PutBucketPolicyHandler(w http.ResponseWriter, r *http.Request) { + objAPI := api.ObjectAPI() + if objAPI == nil { + writeErrorResponse(w, ErrServerNotInitialized, r.URL) + return + } + + if s3Error := checkRequestAuthType(r, "", "", serverConfig.GetRegion()); s3Error != ErrNone { + writeErrorResponse(w, s3Error, r.URL) + return + } + + vars := router.Vars(r) + bucket := vars["bucket"] + + // Before proceeding validate if bucket exists. + _, err := objAPI.GetBucketInfo(bucket) + if err != nil { + errorIf(err, "Unable to find bucket info.") + writeErrorResponse(w, toAPIErrorCode(err), r.URL) + return + } + + // If Content-Length is unknown or zero, deny the + // request. PutBucketPolicy always needs a Content-Length. + if r.ContentLength == -1 || r.ContentLength == 0 { + writeErrorResponse(w, ErrMissingContentLength, r.URL) + return + } + // If Content-Length is greater than maximum allowed policy size. + if r.ContentLength > maxAccessPolicySize { + writeErrorResponse(w, ErrEntityTooLarge, r.URL) + return + } + + // Read access policy up to maxAccessPolicySize. + // http://docs.aws.amazon.com/AmazonS3/latest/dev/access-policy-language-overview.html + // bucket policies are limited to 20KB in size, using a limit reader. + policyBytes, err := ioutil.ReadAll(io.LimitReader(r.Body, maxAccessPolicySize)) + if err != nil { + errorIf(err, "Unable to read from client.") + writeErrorResponse(w, toAPIErrorCode(err), r.URL) + return + } + + { + // FIXME: consolidate bucketPolicy and policy.BucketAccessPolicy so that + // the verification below is done on the same type. + // Parse bucket policy. + policyInfo := &bucketPolicy{} + err = parseBucketPolicy(bytes.NewReader(policyBytes), policyInfo) + if err != nil { + errorIf(err, "Unable to parse bucket policy.") + writeErrorResponse(w, ErrInvalidPolicyDocument, r.URL) + return + } + + // Parse check bucket policy. + if s3Error := checkBucketPolicyResources(bucket, policyInfo); s3Error != ErrNone { + writeErrorResponse(w, toAPIErrorCode(err), r.URL) + return + } + } + policyInfo := &policy.BucketAccessPolicy{} + if err = json.Unmarshal(policyBytes, policyInfo); err != nil { + writeErrorResponse(w, toAPIErrorCode(err), r.URL) + return + } + var policies []BucketAccessPolicy + for prefix, policy := range policy.GetPolicies(policyInfo.Statements, bucket) { + policies = append(policies, BucketAccessPolicy{ + Prefix: prefix, + Policy: policy, + }) + } + if err = objAPI.SetBucketPolicies(bucket, policies); err != nil { + writeErrorResponse(w, toAPIErrorCode(err), r.URL) + return + } + // Success. + writeSuccessNoContent(w) +} + +// DeleteBucketPolicyHandler - DELETE Bucket policy +// ----------------- +// This implementation of the DELETE operation uses the policy +// subresource to add to remove a policy on a bucket. +func (api gatewayAPIHandlers) DeleteBucketPolicyHandler(w http.ResponseWriter, r *http.Request) { + objAPI := api.ObjectAPI() + if objAPI == nil { + writeErrorResponse(w, ErrServerNotInitialized, r.URL) + return + } + + if s3Error := checkRequestAuthType(r, "", "", serverConfig.GetRegion()); s3Error != ErrNone { + writeErrorResponse(w, s3Error, r.URL) + return + } + + vars := router.Vars(r) + bucket := vars["bucket"] + + // Before proceeding validate if bucket exists. + _, err := objAPI.GetBucketInfo(bucket) + if err != nil { + errorIf(err, "Unable to find bucket info.") + writeErrorResponse(w, toAPIErrorCode(err), r.URL) + return + } + + // Delete bucket access policy, by passing an empty policy + // struct. + objAPI.DeleteBucketPolicies(bucket) + // Success. + writeSuccessNoContent(w) +} + +// GetBucketPolicyHandler - GET Bucket policy +// ----------------- +// This operation uses the policy +// subresource to return the policy of a specified bucket. +func (api gatewayAPIHandlers) GetBucketPolicyHandler(w http.ResponseWriter, r *http.Request) { + objAPI := api.ObjectAPI() + if objAPI == nil { + writeErrorResponse(w, ErrServerNotInitialized, r.URL) + return + } + + if s3Error := checkRequestAuthType(r, "", "", serverConfig.GetRegion()); s3Error != ErrNone { + writeErrorResponse(w, s3Error, r.URL) + return + } + + vars := router.Vars(r) + bucket := vars["bucket"] + + // Before proceeding validate if bucket exists. + _, err := objAPI.GetBucketInfo(bucket) + if err != nil { + errorIf(err, "Unable to find bucket info.") + writeErrorResponse(w, toAPIErrorCode(err), r.URL) + return + } + + policies, err := objAPI.GetBucketPolicies(bucket) + if err != nil { + errorIf(err, "Unable to read bucket policy.") + writeErrorResponse(w, toAPIErrorCode(err), r.URL) + return + } + policyInfo := policy.BucketAccessPolicy{Version: "2012-10-17"} + for _, p := range policies { + policyInfo.Statements = policy.SetPolicy(policyInfo.Statements, p.Policy, bucket, p.Prefix) + } + policyBytes, err := json.Marshal(&policyInfo) + if err != nil { + errorIf(err, "Unable to read bucket policy.") + writeErrorResponse(w, toAPIErrorCode(err), r.URL) + return + } + // Write to client. + w.Write(policyBytes) +} + +// GetBucketNotificationHandler - This implementation of the GET +// operation uses the notification subresource to return the +// notification configuration of a bucket. If notifications are +// not enabled on the bucket, the operation returns an empty +// NotificationConfiguration element. +func (api gatewayAPIHandlers) GetBucketNotificationHandler(w http.ResponseWriter, r *http.Request) { + writeErrorResponse(w, ErrNotImplemented, r.URL) +} + +// PutBucketNotificationHandler - Minio notification feature enables +// you to receive notifications when certain events happen in your bucket. +// Using this API, you can replace an existing notification configuration. +// The configuration is an XML file that defines the event types that you +// want Minio to publish and the destination where you want Minio to publish +// an event notification when it detects an event of the specified type. +// By default, your bucket has no event notifications configured. That is, +// the notification configuration will be an empty NotificationConfiguration. +func (api gatewayAPIHandlers) PutBucketNotificationHandler(w http.ResponseWriter, r *http.Request) { + writeErrorResponse(w, ErrNotImplemented, r.URL) +} + +// ListenBucketNotificationHandler - list bucket notifications. +func (api gatewayAPIHandlers) ListenBucketNotificationHandler(w http.ResponseWriter, r *http.Request) { + writeErrorResponse(w, ErrNotImplemented, r.URL) +} + +// DeleteBucketHandler - Delete bucket +func (api gatewayAPIHandlers) DeleteBucketHandler(w http.ResponseWriter, r *http.Request) { + objectAPI := api.ObjectAPI() + if objectAPI == nil { + writeErrorResponse(w, ErrServerNotInitialized, r.URL) + return + } + + // DeleteBucket does not have any bucket action. + if s3Error := checkRequestAuthType(r, "", "", serverConfig.GetRegion()); s3Error != ErrNone { + writeErrorResponse(w, s3Error, r.URL) + return + } + + vars := router.Vars(r) + bucket := vars["bucket"] + + // Attempt to delete bucket. + if err := objectAPI.DeleteBucket(bucket); err != nil { + errorIf(err, "Unable to delete a bucket.") + writeErrorResponse(w, toAPIErrorCode(err), r.URL) + return + } + + // Write success response. + writeSuccessNoContent(w) +} + +// ListObjectsV1Handler - GET Bucket (List Objects) Version 1. +// -------------------------- +// This implementation of the GET operation returns some or all (up to 1000) +// of the objects in a bucket. You can use the request parameters as selection +// criteria to return a subset of the objects in a bucket. +// +func (api gatewayAPIHandlers) ListObjectsV1Handler(w http.ResponseWriter, r *http.Request) { + vars := router.Vars(r) + bucket := vars["bucket"] + + objectAPI := api.ObjectAPI() + if objectAPI == nil { + writeErrorResponse(w, ErrServerNotInitialized, r.URL) + return + } + + reqAuthType := getRequestAuthType(r) + + switch reqAuthType { + case authTypePresignedV2, authTypeSignedV2: + // Signature V2 validation. + s3Error := isReqAuthenticatedV2(r) + if s3Error != ErrNone { + errorIf(errSignatureMismatch, dumpRequest(r)) + writeErrorResponse(w, s3Error, r.URL) + return + } + case authTypeSigned, authTypePresigned: + s3Error := isReqAuthenticated(r, serverConfig.GetRegion()) + if s3Error != ErrNone { + errorIf(errSignatureMismatch, dumpRequest(r)) + writeErrorResponse(w, s3Error, r.URL) + return + } + } + + // Extract all the litsObjectsV1 query params to their native values. + prefix, marker, delimiter, maxKeys, _ := getListObjectsV1Args(r.URL.Query()) + + // Validate all the query params before beginning to serve the request. + if s3Error := validateListObjectsArgs(prefix, marker, delimiter, maxKeys); s3Error != ErrNone { + writeErrorResponse(w, s3Error, r.URL) + return + } + + listObjects := objectAPI.ListObjects + if reqAuthType == authTypeAnonymous { + listObjects = objectAPI.AnonListObjects + } + // Inititate a list objects operation based on the input params. + // On success would return back ListObjectsInfo object to be + // marshalled into S3 compatible XML header. + listObjectsInfo, err := listObjects(bucket, prefix, marker, delimiter, maxKeys) + if err != nil { + errorIf(err, "Unable to list objects.") + writeErrorResponse(w, toAPIErrorCode(err), r.URL) + return + } + response := generateListObjectsV1Response(bucket, prefix, marker, delimiter, maxKeys, listObjectsInfo) + + // Write success response. + writeSuccessResponseXML(w, encodeResponse(response)) +} + +// HeadBucketHandler - HEAD Bucket +// ---------- +// This operation is useful to determine if a bucket exists. +// The operation returns a 200 OK if the bucket exists and you +// have permission to access it. Otherwise, the operation might +// return responses such as 404 Not Found and 403 Forbidden. +func (api gatewayAPIHandlers) HeadBucketHandler(w http.ResponseWriter, r *http.Request) { + vars := router.Vars(r) + bucket := vars["bucket"] + + objectAPI := api.ObjectAPI() + if objectAPI == nil { + writeErrorResponseHeadersOnly(w, ErrServerNotInitialized) + return + } + + reqAuthType := getRequestAuthType(r) + + switch reqAuthType { + case authTypePresignedV2, authTypeSignedV2: + // Signature V2 validation. + s3Error := isReqAuthenticatedV2(r) + if s3Error != ErrNone { + errorIf(errSignatureMismatch, dumpRequest(r)) + writeErrorResponse(w, s3Error, r.URL) + return + } + case authTypeSigned, authTypePresigned: + s3Error := isReqAuthenticated(r, serverConfig.GetRegion()) + if s3Error != ErrNone { + errorIf(errSignatureMismatch, dumpRequest(r)) + writeErrorResponse(w, s3Error, r.URL) + return + } + } + + getBucketInfo := objectAPI.GetBucketInfo + if reqAuthType == authTypeAnonymous { + getBucketInfo = objectAPI.AnonGetBucketInfo + } + + if _, err := getBucketInfo(bucket); err != nil { + errorIf(err, "Unable to fetch bucket info.") + writeErrorResponseHeadersOnly(w, toAPIErrorCode(err)) + return + } + + writeSuccessResponseHeadersOnly(w) +} + +// GetBucketLocationHandler - GET Bucket location. +// ------------------------- +// This operation returns bucket location. +func (api gatewayAPIHandlers) GetBucketLocationHandler(w http.ResponseWriter, r *http.Request) { + vars := router.Vars(r) + bucket := vars["bucket"] + + objectAPI := api.ObjectAPI() + if objectAPI == nil { + writeErrorResponse(w, ErrServerNotInitialized, r.URL) + return + } + reqAuthType := getRequestAuthType(r) + + switch reqAuthType { + case authTypePresignedV2, authTypeSignedV2: + // Signature V2 validation. + s3Error := isReqAuthenticatedV2(r) + if s3Error != ErrNone { + errorIf(errSignatureMismatch, dumpRequest(r)) + writeErrorResponse(w, s3Error, r.URL) + return + } + case authTypeSigned, authTypePresigned: + s3Error := isReqAuthenticated(r, globalMinioDefaultRegion) + if s3Error == ErrInvalidRegion { + // Clients like boto3 send getBucketLocation() call signed with region that is configured. + s3Error = isReqAuthenticated(r, serverConfig.GetRegion()) + } + if s3Error != ErrNone { + errorIf(errSignatureMismatch, dumpRequest(r)) + writeErrorResponse(w, s3Error, r.URL) + return + } + } + + getBucketInfo := objectAPI.GetBucketInfo + if reqAuthType == authTypeAnonymous { + getBucketInfo = objectAPI.AnonGetBucketInfo + } + + if _, err := getBucketInfo(bucket); err != nil { + errorIf(err, "Unable to fetch bucket info.") + writeErrorResponse(w, toAPIErrorCode(err), r.URL) + return + } + + // Generate response. + encodedSuccessResponse := encodeResponse(LocationResponse{}) + // Get current region. + region := serverConfig.GetRegion() + if region != globalMinioDefaultRegion { + encodedSuccessResponse = encodeResponse(LocationResponse{ + Location: region, + }) + } + + // Write success response. + writeSuccessResponseXML(w, encodedSuccessResponse) +} diff --git a/cmd/gateway-main.go b/cmd/gateway-main.go new file mode 100644 index 000000000..a38759ec4 --- /dev/null +++ b/cmd/gateway-main.go @@ -0,0 +1,219 @@ +/* + * Minio Cloud Storage, (C) 2017 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cmd + +import ( + "fmt" + "os" + + "github.com/gorilla/mux" + "github.com/minio/cli" + "github.com/minio/mc/pkg/console" +) + +var gatewayTemplate = `NAME: + {{.HelpName}} - {{.Usage}} + +USAGE: + {{.HelpName}} {{if .VisibleFlags}}[FLAGS]{{end}} BACKEND +{{if .VisibleFlags}} +FLAGS: + {{range .VisibleFlags}}{{.}} + {{end}}{{end}} +ENVIRONMENT VARIABLES: + ACCESS: + MINIO_ACCESS_KEY: Username or access key of your storage backend. + MINIO_SECRET_KEY: Password or secret key of your storage backend. + +EXAMPLES: + 1. Start minio gateway server for Azure Blob Storage backend. + $ {{.HelpName}} azure + + 2. Start minio gateway server bound to a specific ADDRESS:PORT. + $ {{.HelpName}} --address 192.168.1.101:9000 azure +` + +var gatewayCmd = cli.Command{ + Name: "gateway", + Usage: "Start object storage gateway server.", + Action: gatewayMain, + CustomHelpTemplate: gatewayTemplate, + Flags: append(serverFlags, cli.BoolFlag{ + Name: "quiet", + Usage: "Disable startup banner.", + }), + HideHelpCommand: true, +} + +// Represents the type of the gateway backend. +type gatewayBackend string + +const ( + azureBackend gatewayBackend = "azure" + // Add more backends here. +) + +// Returns access and secretkey set from environment variables. +func mustGetGatewayCredsFromEnv() (accessKey, secretKey string) { + // Fetch access keys from environment variables. + accessKey = os.Getenv("MINIO_ACCESS_KEY") + secretKey = os.Getenv("MINIO_SECRET_KEY") + if accessKey == "" || secretKey == "" { + console.Fatalln("Access and secret keys are mandatory to run Minio gateway server.") + } + return accessKey, secretKey +} + +// Initialize gateway layer depending on the backend type. +// Supported backend types are +// +// - Azure Blob Storage. +// - Add your favorite backend here. +func newGatewayLayer(backendType, accessKey, secretKey string) (GatewayLayer, error) { + if gatewayBackend(backendType) != azureBackend { + return nil, fmt.Errorf("Unrecognized backend type %s", backendType) + } + return newAzureLayer(accessKey, secretKey) +} + +// Initialize a new gateway config. +// +// DO NOT save this config, this is meant to be +// only used in memory. +func newGatewayConfig(accessKey, secretKey, region string) error { + // Initialize server config. + srvCfg := newServerConfigV14() + + // If env is set for a fresh start, save them to config file. + srvCfg.SetCredential(credential{ + AccessKey: accessKey, + SecretKey: secretKey, + }) + + // Set default printing to console. + srvCfg.Logger.SetConsole(consoleLogger{true, "error"}) + + // Set custom region. + srvCfg.SetRegion(region) + + // Create certs path for SSL configuration. + if err := createConfigDir(); err != nil { + return err + } + + // hold the mutex lock before a new config is assigned. + // Save the new config globally. + // unlock the mutex. + serverConfigMu.Lock() + serverConfig = srvCfg + serverConfigMu.Unlock() + + return nil +} + +// Handler for 'minio gateway'. +func gatewayMain(ctx *cli.Context) { + if !ctx.Args().Present() || ctx.Args().First() == "help" { + cli.ShowCommandHelpAndExit(ctx, "gateway", 1) + } + + // Fetch access and secret key from env. + accessKey, secretKey := mustGetGatewayCredsFromEnv() + + // Initialize new gateway config. + // + // TODO: add support for custom region when we add + // support for S3 backend storage, currently this can + // default to "us-east-1" + err := newGatewayConfig(accessKey, secretKey, "us-east-1") + if err != nil { + console.Fatalf("Unable to initialize gateway config. Error: %s", err) + } + + // Enable console logging. + enableConsoleLogger() + + // Get quiet flag from command line argument. + quietFlag := ctx.Bool("quiet") || ctx.GlobalBool("quiet") + + // First argument is selected backend type. + backendType := ctx.Args().First() + + newObject, err := newGatewayLayer(backendType, accessKey, secretKey) + if err != nil { + console.Fatalf("Unable to initialize gateway layer. Error: %s", err) + } + + initNSLock(false) // Enable local namespace lock. + + router := mux.NewRouter().SkipClean(true) + registerGatewayAPIRouter(router, newObject) + + var handlerFns = []HandlerFunc{ + // Limits all requests size to a maximum fixed limit + setRequestSizeLimitHandler, + // Adds 'crossdomain.xml' policy handler to serve legacy flash clients. + setCrossDomainPolicy, + // Validates all incoming requests to have a valid date header. + setTimeValidityHandler, + // CORS setting for all browser API requests. + setCorsHandler, + // Validates all incoming URL resources, for invalid/unsupported + // resources client receives a HTTP error. + setIgnoreResourcesHandler, + // Auth handler verifies incoming authorization headers and + // routes them accordingly. Client receives a HTTP error for + // invalid/unsupported signatures. + setAuthHandler, + } + + apiServer := NewServerMux(ctx.String("address"), registerHandlers(router, handlerFns...)) + + // Set if we are SSL enabled S3 gateway. + globalIsSSL = isSSL() + + // Start server, automatically configures TLS if certs are available. + go func() { + cert, key := "", "" + if globalIsSSL { + cert, key = getPublicCertFile(), getPrivateKeyFile() + } + if aerr := apiServer.ListenAndServe(cert, key); aerr != nil { + console.Fatalf("Failed to start minio server. Error: %s\n", aerr) + } + }() + + apiEndPoints, err := finalizeAPIEndpoints(apiServer.Addr) + fatalIf(err, "Unable to finalize API endpoints for %s", apiServer.Addr) + + // Once endpoints are finalized, initialize the new object api. + globalObjLayerMutex.Lock() + globalObjectAPI = newObject + globalObjLayerMutex.Unlock() + + // Prints the formatted startup message once object layer is initialized. + if !quietFlag { + mode := "" + if gatewayBackend(backendType) == azureBackend { + mode = globalMinioModeGatewayAzure + } + checkUpdate(mode) + printGatewayStartupMessage(apiEndPoints, accessKey, secretKey, backendType) + } + + <-globalServiceDoneCh +} diff --git a/cmd/gateway-router.go b/cmd/gateway-router.go new file mode 100644 index 000000000..6ff0f1913 --- /dev/null +++ b/cmd/gateway-router.go @@ -0,0 +1,122 @@ +/* + * Minio Cloud Storage, (C) 2017 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cmd + +import ( + "io" + + router "github.com/gorilla/mux" +) + +// GatewayLayer - Interface to implement gateway mode. +type GatewayLayer interface { + ObjectLayer + AnonGetObject(bucket, object string, startOffset int64, length int64, writer io.Writer) (err error) + AnonGetObjectInfo(bucket, object string) (objInfo ObjectInfo, err error) + SetBucketPolicies(string, []BucketAccessPolicy) error + GetBucketPolicies(string) ([]BucketAccessPolicy, error) + DeleteBucketPolicies(string) error + AnonListObjects(bucket, prefix, marker, delimiter string, maxKeys int) (result ListObjectsInfo, err error) + AnonGetBucketInfo(bucket string) (bucketInfo BucketInfo, err error) +} + +// Implements and provides http handlers for S3 API. +// Overrides GetObject HeadObject and Policy related handlers. +type gatewayAPIHandlers struct { + objectAPIHandlers + ObjectAPI func() GatewayLayer +} + +// registerAPIRouter - registers S3 compatible APIs. +func registerGatewayAPIRouter(mux *router.Router, gw GatewayLayer) { + // Initialize API. + api := gatewayAPIHandlers{ + ObjectAPI: func() GatewayLayer { return gw }, + objectAPIHandlers: objectAPIHandlers{ + ObjectAPI: newObjectLayerFn, + }, + } + + // API Router + apiRouter := mux.NewRoute().PathPrefix("/").Subrouter() + + // Bucket router + bucket := apiRouter.PathPrefix("/{bucket}").Subrouter() + + /// Object operations + + // HeadObject + bucket.Methods("HEAD").Path("/{object:.+}").HandlerFunc(api.HeadObjectHandler) + // CopyObjectPart + bucket.Methods("PUT").Path("/{object:.+}").HeadersRegexp("X-Amz-Copy-Source", ".*?(\\/|%2F).*?").HandlerFunc(api.CopyObjectPartHandler).Queries("partNumber", "{partNumber:[0-9]+}", "uploadId", "{uploadId:.*}") + // PutObjectPart + bucket.Methods("PUT").Path("/{object:.+}").HandlerFunc(api.PutObjectPartHandler).Queries("partNumber", "{partNumber:[0-9]+}", "uploadId", "{uploadId:.*}") + // ListObjectPxarts + bucket.Methods("GET").Path("/{object:.+}").HandlerFunc(api.ListObjectPartsHandler).Queries("uploadId", "{uploadId:.*}") + // CompleteMultipartUpload + bucket.Methods("POST").Path("/{object:.+}").HandlerFunc(api.CompleteMultipartUploadHandler).Queries("uploadId", "{uploadId:.*}") + // NewMultipartUpload + bucket.Methods("POST").Path("/{object:.+}").HandlerFunc(api.NewMultipartUploadHandler).Queries("uploads", "") + // AbortMultipartUpload + bucket.Methods("DELETE").Path("/{object:.+}").HandlerFunc(api.AbortMultipartUploadHandler).Queries("uploadId", "{uploadId:.*}") + // GetObject + bucket.Methods("GET").Path("/{object:.+}").HandlerFunc(api.GetObjectHandler) + // CopyObject + bucket.Methods("PUT").Path("/{object:.+}").HeadersRegexp("X-Amz-Copy-Source", ".*?(\\/|%2F).*?").HandlerFunc(api.CopyObjectHandler) + // PutObject + bucket.Methods("PUT").Path("/{object:.+}").HandlerFunc(api.PutObjectHandler) + // DeleteObject + bucket.Methods("DELETE").Path("/{object:.+}").HandlerFunc(api.DeleteObjectHandler) + + /// Bucket operations + + // GetBucketLocation + bucket.Methods("GET").HandlerFunc(api.GetBucketLocationHandler).Queries("location", "") + // GetBucketPolicy + bucket.Methods("GET").HandlerFunc(api.GetBucketPolicyHandler).Queries("policy", "") + // GetBucketNotification + bucket.Methods("GET").HandlerFunc(api.GetBucketNotificationHandler).Queries("notification", "") + // ListenBucketNotification + bucket.Methods("GET").HandlerFunc(api.ListenBucketNotificationHandler).Queries("events", "{events:.*}") + // ListMultipartUploads + bucket.Methods("GET").HandlerFunc(api.ListMultipartUploadsHandler).Queries("uploads", "") + // ListObjectsV2 + bucket.Methods("GET").HandlerFunc(api.ListObjectsV2Handler).Queries("list-type", "2") + // ListObjectsV1 (Legacy) + bucket.Methods("GET").HandlerFunc(api.ListObjectsV1Handler) + // PutBucketPolicy + bucket.Methods("PUT").HandlerFunc(api.PutBucketPolicyHandler).Queries("policy", "") + // PutBucketNotification + bucket.Methods("PUT").HandlerFunc(api.PutBucketNotificationHandler).Queries("notification", "") + // PutBucket + bucket.Methods("PUT").HandlerFunc(api.PutBucketHandler) + // HeadBucket + bucket.Methods("HEAD").HandlerFunc(api.HeadBucketHandler) + // PostPolicy + bucket.Methods("POST").HeadersRegexp("Content-Type", "multipart/form-data*").HandlerFunc(api.PostPolicyBucketHandler) + // DeleteMultipleObjects + bucket.Methods("POST").HandlerFunc(api.DeleteMultipleObjectsHandler) + // DeleteBucketPolicy + bucket.Methods("DELETE").HandlerFunc(api.DeleteBucketPolicyHandler).Queries("policy", "") + // DeleteBucket + bucket.Methods("DELETE").HandlerFunc(api.DeleteBucketHandler) + + /// Root operation + + // ListBuckets + apiRouter.Methods("GET").HandlerFunc(api.ListBucketsHandler) +} diff --git a/cmd/gateway-startup-msg.go b/cmd/gateway-startup-msg.go new file mode 100644 index 000000000..ade9c451d --- /dev/null +++ b/cmd/gateway-startup-msg.go @@ -0,0 +1,67 @@ +/* + * Minio Cloud Storage, (C) 2016, 2017 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cmd + +import ( + "fmt" + "runtime" + "strings" + + "github.com/minio/mc/pkg/console" +) + +// Prints the formatted startup message. +func printGatewayStartupMessage(apiEndPoints []string, accessKey, secretKey, backendType string) { + // Prints credential. + printGatewayCommonMsg(apiEndPoints, accessKey, secretKey) + + // Prints `mc` cli configuration message chooses + // first endpoint as default. + endPoint := apiEndPoints[0] + + // Configure 'mc', following block prints platform specific information for minio client. + console.Println(colorBlue("\nCommand-line Access: ") + mcQuickStartGuide) + if runtime.GOOS == globalWindowsOSName { + mcMessage := fmt.Sprintf("$ mc.exe config host add my%s %s %s %s", backendType, endPoint, accessKey, secretKey) + console.Println(fmt.Sprintf(getFormatStr(len(mcMessage), 3), mcMessage)) + } else { + mcMessage := fmt.Sprintf("$ mc config host add my%s %s %s %s", backendType, endPoint, accessKey, secretKey) + console.Println(fmt.Sprintf(getFormatStr(len(mcMessage), 3), mcMessage)) + } + + // Prints documentation message. + printObjectAPIMsg() + + // SSL is configured reads certification chain, prints + // authority and expiry. + if globalIsSSL { + certs, err := readCertificateChain() + if err != nil { + console.Fatalf("Unable to read certificate chain. Error: %s", err) + } + printCertificateMsg(certs) + } +} + +// Prints common server startup message. Prints credential, region and browser access. +func printGatewayCommonMsg(apiEndpoints []string, accessKey, secretKey string) { + apiEndpointStr := strings.Join(apiEndpoints, " ") + // Colorize the message and print. + console.Println(colorBlue("\nEndpoint: ") + colorBold(fmt.Sprintf(getFormatStr(len(apiEndpointStr), 1), apiEndpointStr))) + console.Println(colorBlue("AccessKey: ") + colorBold(fmt.Sprintf("%s ", accessKey))) + console.Println(colorBlue("SecretKey: ") + colorBold(fmt.Sprintf("%s ", secretKey))) +} diff --git a/cmd/commands.go b/cmd/gateway-startup-msg_test.go similarity index 55% rename from cmd/commands.go rename to cmd/gateway-startup-msg_test.go index b74f33f4a..c3bf2e6cb 100644 --- a/cmd/commands.go +++ b/cmd/gateway-startup-msg_test.go @@ -1,5 +1,5 @@ /* - * Minio Cloud Storage, (C) 2015 Minio, Inc. + * Minio Cloud Storage, (C) 2016, 2017 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,19 +16,16 @@ package cmd -import ( - "github.com/minio/cli" - "github.com/minio/minio/pkg/trie" -) +import "testing" -// Collection of minio commands currently supported are. -var commands = []cli.Command{} - -// Collection of minio commands currently supported in a trie tree. -var commandsTree = trie.NewTrie() - -// registerCommand registers a cli command. -func registerCommand(command cli.Command) { - commands = append(commands, command) - commandsTree.Insert(command.Name) +// Test printing Gateway common message. +func TestPrintGatewayCommonMessage(t *testing.T) { + apiEndpoints := []string{"127.0.0.1:9000"} + printGatewayCommonMsg(apiEndpoints, "abcd1", "abcd123") +} + +// Test print gateway startup message. +func TestPrintGatewayStartupMessage(t *testing.T) { + apiEndpoints := []string{"127.0.0.1:9000"} + printGatewayStartupMessage(apiEndpoints, "abcd1", "abcd123", "azure") } diff --git a/cmd/generic-handlers.go b/cmd/generic-handlers.go index 0bf9fd9da..1f69e5930 100644 --- a/cmd/generic-handlers.go +++ b/cmd/generic-handlers.go @@ -20,7 +20,6 @@ import ( "bufio" "net" "net/http" - "path" "strings" "time" @@ -47,9 +46,9 @@ func registerHandlers(mux *router.Router, handlerFns ...HandlerFunc) http.Handle // which is more than enough to accommodate any form data fields and headers. const requestFormDataSize = 64 * humanize.MiByte -// For any HTTP request, request body should be not more than 5GiB + requestFormDataSize -// where, 5GiB is the maximum allowed object size for object upload. -const requestMaxBodySize = 5*humanize.GiByte + requestFormDataSize +// For any HTTP request, request body should be not more than 16GiB + requestFormDataSize +// where, 16GiB is the maximum allowed object size for object upload. +const requestMaxBodySize = globalMaxObjectSize + requestFormDataSize type requestSizeLimitHandler struct { handler http.Handler @@ -68,7 +67,8 @@ func (h requestSizeLimitHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques // Reserved bucket. const ( - reservedBucket = "/minio" + minioReservedBucket = "minio" + minioReservedBucketPath = "/" + minioReservedBucket ) // Adds redirect rules for incoming requests. @@ -86,8 +86,8 @@ func setBrowserRedirectHandler(h http.Handler) http.Handler { // serves only limited purpose on redirect-handler for // browser requests. func getRedirectLocation(urlPath string) (rLocation string) { - if urlPath == reservedBucket { - rLocation = reservedBucket + "/" + if urlPath == minioReservedBucketPath { + rLocation = minioReservedBucketPath + "/" } if contains([]string{ "/", @@ -95,7 +95,7 @@ func getRedirectLocation(urlPath string) (rLocation string) { "/login", "/favicon.ico", }, urlPath) { - rLocation = reservedBucket + urlPath + rLocation = minioReservedBucketPath + urlPath } return rLocation } @@ -143,8 +143,8 @@ func setBrowserCacheControlHandler(h http.Handler) http.Handler { func (h cacheControlHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if r.Method == httpGET && guessIsBrowserReq(r) && globalIsBrowserEnabled { // For all browser requests set appropriate Cache-Control policies - if hasPrefix(r.URL.Path, reservedBucket+"/") { - if hasSuffix(r.URL.Path, ".js") || r.URL.Path == reservedBucket+"/favicon.ico" { + if hasPrefix(r.URL.Path, minioReservedBucketPath+"/") { + if hasSuffix(r.URL.Path, ".js") || r.URL.Path == minioReservedBucketPath+"/favicon.ico" { // For assets set cache expiry of one year. For each release, the name // of the asset name will change and hence it can not be served from cache. w.Header().Set("Cache-Control", "max-age=31536000") @@ -160,17 +160,17 @@ func (h cacheControlHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // Adds verification for incoming paths. type minioPrivateBucketHandler struct { - handler http.Handler - reservedBucket string + handler http.Handler } func setPrivateBucketHandler(h http.Handler) http.Handler { - return minioPrivateBucketHandler{handler: h, reservedBucket: reservedBucket} + return minioPrivateBucketHandler{h} } func (h minioPrivateBucketHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - // For all non browser requests, reject access to 'reservedBucket'. - if !guessIsBrowserReq(r) && path.Clean(r.URL.Path) == reservedBucket { + // For all non browser requests, reject access to 'minioReservedBucketPath'. + bucketName, _ := urlPath2BucketObjectName(r.URL) + if !guessIsBrowserReq(r) && isMinioReservedBucket(bucketName) && isMinioMetaBucket(bucketName) { writeErrorResponse(w, ErrAllAccessDisabled, r.URL) return } @@ -350,7 +350,7 @@ func (h resourceHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } } // A put method on path "/" doesn't make sense, ignore it. - if r.Method == httpPUT && r.URL.Path == "/" { + if r.Method == httpPUT && r.URL.Path == "/" && r.Header.Get(minioAdminOpHeader) == "" { writeErrorResponse(w, ErrNotImplemented, r.URL) return } diff --git a/cmd/generic-handlers_test.go b/cmd/generic-handlers_test.go index f1645d030..f9872709e 100644 --- a/cmd/generic-handlers_test.go +++ b/cmd/generic-handlers_test.go @@ -29,28 +29,28 @@ func TestRedirectLocation(t *testing.T) { }{ { // 1. When urlPath is '/minio' - urlPath: reservedBucket, - location: reservedBucket + "/", + urlPath: minioReservedBucketPath, + location: minioReservedBucketPath + "/", }, { // 2. When urlPath is '/' urlPath: "/", - location: reservedBucket + "/", + location: minioReservedBucketPath + "/", }, { // 3. When urlPath is '/webrpc' urlPath: "/webrpc", - location: reservedBucket + "/webrpc", + location: minioReservedBucketPath + "/webrpc", }, { // 4. When urlPath is '/login' urlPath: "/login", - location: reservedBucket + "/login", + location: minioReservedBucketPath + "/login", }, { // 5. When urlPath is '/favicon.ico' urlPath: "/favicon.ico", - location: reservedBucket + "/favicon.ico", + location: minioReservedBucketPath + "/favicon.ico", }, { // 6. When urlPath is '/unknown' diff --git a/cmd/globals.go b/cmd/globals.go index 56439f635..ba3f3f286 100644 --- a/cmd/globals.go +++ b/cmd/globals.go @@ -19,27 +19,15 @@ package cmd import ( "crypto/x509" "net/url" - "os" "runtime" - "strings" "time" humanize "github.com/dustin/go-humanize" "github.com/fatih/color" - "github.com/minio/cli" - "github.com/minio/mc/pkg/console" - "github.com/minio/minio/pkg/objcache" ) // minio configuration related constants. const ( - globalMinioConfigVersion = "13" - globalMinioConfigDir = ".minio" - globalMinioCertsDir = "certs" - globalMinioCertsCADir = "CAs" - globalMinioCertFile = "public.crt" - globalMinioKeyFile = "private.key" - globalMinioConfigFile = "config.json" globalMinioCertExpireWarnDays = time.Hour * 24 * 30 // 30 days. globalMinioDefaultRegion = "us-east-1" @@ -48,6 +36,10 @@ const ( globalWindowsOSName = "windows" globalNetBSDOSName = "netbsd" globalSolarisOSName = "solaris" + globalMinioModeFS = "mode-server-fs" + globalMinioModeXL = "mode-server-xl" + globalMinioModeDistXL = "mode-server-distributed-xl" + globalMinioModeGatewayAzure = "mode-gateway-azure" // Add new global values here. ) @@ -64,30 +56,22 @@ const ( ) var ( - globalQuiet = false // quiet flag set via command line. - globalConfigDir = mustGetConfigPath() // config-dir flag set via command line - // Add new global flags here. - // Indicates if the running minio server is distributed setup. globalIsDistXL = false // Indicates if the running minio server is an erasure-code backend. globalIsXL = false - // This flag is set to 'true' by default, it is set to `false` - // when MINIO_BROWSER env is set to 'off'. - globalIsBrowserEnabled = !strings.EqualFold(os.Getenv("MINIO_BROWSER"), "off") - - // Maximum cache size. Defaults to disabled. - // Caching is enabled only for RAM size > 8GiB. - globalMaxCacheSize = uint64(0) + // This flag is set to 'true' by default + globalIsBrowserEnabled = true + // This flag is set to 'true' when MINIO_BROWSER env is set. + globalIsEnvBrowser = false + // Set to true if credentials were passed from env, default is false. + globalIsEnvCreds = false // Maximum size of internal objects parts globalPutPartSize = int64(64 * 1024 * 1024) - // Cache expiry. - globalCacheExpiry = objcache.DefaultExpiry - // Minio local server address (in `host:port` format) globalMinioAddr = "" // Minio default port, can be changed through command line. @@ -113,9 +97,6 @@ var ( // Minio server user agent string. globalServerUserAgent = "Minio/" + ReleaseTag + " (" + runtime.GOOS + "; " + runtime.GOARCH + ")" - // Set to true if credentials were passed from env, default is false. - globalIsEnvCreds = false - // url.URL endpoints of disks that belong to the object storage. globalEndpoints = []*url.URL{} @@ -142,20 +123,3 @@ var ( colorBold = color.New(color.Bold).SprintFunc() colorBlue = color.New(color.FgBlue).SprintfFunc() ) - -// Parse command arguments and set global variables accordingly -func setGlobalsFromContext(c *cli.Context) { - // Set config dir - switch { - case c.IsSet("config-dir"): - globalConfigDir = c.String("config-dir") - case c.GlobalIsSet("config-dir"): - globalConfigDir = c.GlobalString("config-dir") - } - if globalConfigDir == "" { - console.Fatalf("Unable to get config file. Config directory is empty.") - } - - // Set global quiet flag. - globalQuiet = c.Bool("quiet") || c.GlobalBool("quiet") -} diff --git a/cmd/handler-utils.go b/cmd/handler-utils.go index 3b0e852e1..19319f6a4 100644 --- a/cmd/handler-utils.go +++ b/cmd/handler-utils.go @@ -20,6 +20,7 @@ import ( "io" "mime/multipart" "net/http" + "net/url" "strings" ) @@ -105,6 +106,9 @@ func path2BucketAndObject(path string) (bucket, object string) { // extractMetadataFromHeader extracts metadata from HTTP header. func extractMetadataFromHeader(header http.Header) map[string]string { + if header == nil { + return nil + } metadata := make(map[string]string) // Save standard supported headers. for _, supportedHeader := range supportedHeaders { @@ -126,51 +130,67 @@ func extractMetadataFromHeader(header http.Header) map[string]string { metadata[cKey] = header.Get(key) } } - // Return. + + // Success. return metadata } +// The Query string for the redirect URL the client is +// redirected on successful upload. +func getRedirectPostRawQuery(objInfo ObjectInfo) string { + redirectValues := make(url.Values) + redirectValues.Set("bucket", objInfo.Bucket) + redirectValues.Set("key", objInfo.Name) + redirectValues.Set("etag", "\""+objInfo.MD5Sum+"\"") + return redirectValues.Encode() +} + +// Extract request params to be sent with event notifiation. +func extractReqParams(r *http.Request) map[string]string { + if r == nil { + return nil + } + + // Success. + return map[string]string{ + "sourceIPAddress": r.RemoteAddr, + // Add more fields here. + } +} + // extractMetadataFromForm extracts metadata from Post Form. -func extractMetadataFromForm(formValues map[string]string) map[string]string { - metadata := make(map[string]string) - // Save standard supported headers. - for _, supportedHeader := range supportedHeaders { - canonicalHeader := http.CanonicalHeaderKey(supportedHeader) - // Form field names are case insensitive, look for both canonical - // and non canonical entries. - if _, ok := formValues[canonicalHeader]; ok { - metadata[supportedHeader] = formValues[canonicalHeader] - } else if _, ok := formValues[supportedHeader]; ok { - metadata[supportedHeader] = formValues[canonicalHeader] +func extractMetadataFromForm(formValues http.Header) map[string]string { + return extractMetadataFromHeader(formValues) +} + +// Validate form field size for s3 specification requirement. +func validateFormFieldSize(formValues http.Header) error { + // Iterate over form values + for k := range formValues { + // Check if value's field exceeds S3 limit + if int64(len(formValues.Get(k))) > maxFormFieldSize { + return traceError(errSizeUnexpected) } } - // Go through all other form values for any additional headers that needs to be saved. - for key := range formValues { - cKey := http.CanonicalHeaderKey(key) - if strings.HasPrefix(cKey, "X-Amz-Meta-") { - metadata[cKey] = formValues[key] - } else if strings.HasPrefix(cKey, "X-Minio-Meta-") { - metadata[cKey] = formValues[key] - } - } - return metadata + + // Success. + return nil } // Extract form fields and file data from a HTTP POST Policy -func extractPostPolicyFormValues(form *multipart.Form) (filePart io.ReadCloser, fileName string, fileSize int64, formValues map[string]string, err error) { +func extractPostPolicyFormValues(form *multipart.Form) (filePart io.ReadCloser, fileName string, fileSize int64, formValues http.Header, err error) { /// HTML Form values - formValues = make(map[string]string) fileName = "" - // Iterate over form values + // Canonicalize the form values into http.Header. + formValues = make(http.Header) for k, v := range form.Value { - canonicalFormName := http.CanonicalHeaderKey(k) - // Check if value's field exceeds S3 limit - if int64(len(v[0])) > maxFormFieldSize { - return nil, "", 0, nil, traceError(errSizeUnexpected) - } - // Set the form value - formValues[canonicalFormName] = v[0] + formValues[http.CanonicalHeaderKey(k)] = v + } + + // Validate form values. + if err = validateFormFieldSize(formValues); err != nil { + return nil, "", 0, nil, err } // Iterator until we find a valid File field and break diff --git a/cmd/handler-utils_test.go b/cmd/handler-utils_test.go index 0bf34b3c5..3c0b4c2a8 100644 --- a/cmd/handler-utils_test.go +++ b/cmd/handler-utils_test.go @@ -22,6 +22,7 @@ import ( "io/ioutil" "net/http" "reflect" + "strings" "testing" ) @@ -83,6 +84,44 @@ func TestIsValidLocationContraint(t *testing.T) { } } +// Test validate form field size. +func TestValidateFormFieldSize(t *testing.T) { + testCases := []struct { + header http.Header + err error + }{ + // Empty header returns error as nil, + { + header: nil, + err: nil, + }, + // Valid header returns error as nil. + { + header: http.Header{ + "Content-Type": []string{"image/png"}, + }, + err: nil, + }, + // Invalid header value > maxFormFieldSize+1 + { + header: http.Header{ + "Garbage": []string{strings.Repeat("a", int(maxFormFieldSize)+1)}, + }, + err: errSizeUnexpected, + }, + } + + // Run validate form field size check under all test cases. + for i, testCase := range testCases { + err := validateFormFieldSize(testCase.header) + if err != nil { + if errorCause(err).Error() != testCase.err.Error() { + t.Errorf("Test %d: Expected error %s, got %s", i+1, testCase.err, err) + } + } + } +} + // Tests validate metadata extraction from http headers. func TestExtractMetadataHeaders(t *testing.T) { testCases := []struct { @@ -115,6 +154,11 @@ func TestExtractMetadataHeaders(t *testing.T) { "X-Amz-Meta-Appid": "amz-meta", "X-Minio-Meta-Appid": "minio-meta"}, }, + // Empty header input returns empty metadata. + { + header: nil, + metadata: nil, + }, } // Validate if the extracting headers. diff --git a/cmd/humanized-duration_test.go b/cmd/humanized-duration_test.go index 9f51b1296..193710767 100644 --- a/cmd/humanized-duration_test.go +++ b/cmd/humanized-duration_test.go @@ -17,7 +17,6 @@ package cmd import ( - "strings" "testing" "time" ) @@ -26,10 +25,10 @@ import ( func TestHumanizedDuration(t *testing.T) { duration := time.Duration(90487000000000) humanDuration := timeDurationToHumanizedDuration(duration) - if !strings.HasSuffix(humanDuration.String(), "seconds") { + if !hasSuffix(humanDuration.String(), "seconds") { t.Fatal("Stringer method for humanized duration should have seconds.", humanDuration.String()) } - if strings.HasSuffix(humanDuration.StringShort(), "seconds") { + if hasSuffix(humanDuration.StringShort(), "seconds") { t.Fatal("StringShorter method for humanized duration should not have seconds.", humanDuration.StringShort()) } @@ -42,9 +41,9 @@ func TestHumanizedDuration(t *testing.T) { t.Fatalf("Expected %#v, got %#v incorrect conversion of duration to humanized form", expectedHumanSecDuration, humanSecDuration) } - if strings.HasSuffix(humanSecDuration.String(), "days") || - strings.HasSuffix(humanSecDuration.String(), "hours") || - strings.HasSuffix(humanSecDuration.String(), "minutes") { + if hasSuffix(humanSecDuration.String(), "days") || + hasSuffix(humanSecDuration.String(), "hours") || + hasSuffix(humanSecDuration.String(), "minutes") { t.Fatal("Stringer method for humanized duration should have only seconds.", humanSecDuration.String()) } @@ -57,7 +56,7 @@ func TestHumanizedDuration(t *testing.T) { t.Fatalf("Expected %#v, got %#v incorrect conversion of duration to humanized form", expectedHumanMinDuration, humanMinDuration) } - if strings.HasSuffix(humanMinDuration.String(), "hours") { + if hasSuffix(humanMinDuration.String(), "hours") { t.Fatal("Stringer method for humanized duration should have only minutes.", humanMinDuration.String()) } @@ -70,7 +69,7 @@ func TestHumanizedDuration(t *testing.T) { t.Fatalf("Expected %#v, got %#v incorrect conversion of duration to humanized form", expectedHumanHourDuration, humanHourDuration) } - if strings.HasSuffix(humanHourDuration.String(), "days") { + if hasSuffix(humanHourDuration.String(), "days") { t.Fatal("Stringer method for humanized duration should have hours.", humanHourDuration.String()) } } diff --git a/cmd/jwt.go b/cmd/jwt.go index 54f4668f7..430cada9f 100644 --- a/cmd/jwt.go +++ b/cmd/jwt.go @@ -1,5 +1,5 @@ /* - * Minio Cloud Storage, (C) 2016 Minio, Inc. + * Minio Cloud Storage, (C) 2016, 2017 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,12 +20,10 @@ import ( "errors" "fmt" "net/http" - "strings" "time" jwtgo "github.com/dgrijalva/jwt-go" jwtreq "github.com/dgrijalva/jwt-go/request" - "golang.org/x/crypto/bcrypt" ) const ( @@ -39,9 +37,6 @@ const ( ) var ( - errInvalidAccessKeyLength = errors.New("Invalid access key, access key should be 5 to 20 characters in length") - errInvalidSecretKeyLength = errors.New("Invalid secret key, secret key should be 8 to 40 characters in length") - errInvalidAccessKeyID = errors.New("The access key ID you provided does not exist in our records") errChangeCredNotAllowed = errors.New("Changing access key and secret key not allowed") errAuthentication = errors.New("Authentication failed, check your access credentials") @@ -49,34 +44,19 @@ var ( ) func authenticateJWT(accessKey, secretKey string, expiry time.Duration) (string, error) { - // Trim spaces. - accessKey = strings.TrimSpace(accessKey) - - if !isAccessKeyValid(accessKey) { - return "", errInvalidAccessKeyLength - } - if !isSecretKeyValid(secretKey) { - return "", errInvalidSecretKeyLength + passedCredential, err := createCredential(accessKey, secretKey) + if err != nil { + return "", err } serverCred := serverConfig.GetCredential() - // Validate access key. - if accessKey != serverCred.AccessKey { + if serverCred.AccessKey != passedCredential.AccessKey { return "", errInvalidAccessKeyID } - // Validate secret key. - // Using bcrypt to avoid timing attacks. - if serverCred.secretKeyHash != nil { - if bcrypt.CompareHashAndPassword(serverCred.secretKeyHash, []byte(secretKey)) != nil { - return "", errAuthentication - } - } else { - // Secret key hash not set then generate and validate. - if bcrypt.CompareHashAndPassword(mustGetHashedSecretKey(serverCred.SecretKey), []byte(secretKey)) != nil { - return "", errAuthentication - } + if !serverCred.Equal(passedCredential) { + return "", errAuthentication } utcNow := time.Now().UTC() diff --git a/cmd/jwt_test.go b/cmd/jwt_test.go index c8b531269..7a101252e 100644 --- a/cmd/jwt_test.go +++ b/cmd/jwt_test.go @@ -39,6 +39,8 @@ func testAuthenticate(authType string, t *testing.T) { {"user12345678901234567", "pass", errInvalidAccessKeyLength}, // Access key contains unsuppported characters. {"!@#$", "pass", errInvalidAccessKeyLength}, + // Success when access key contains leading/trailing spaces. + {" " + serverCred.AccessKey + " ", serverCred.SecretKey, errInvalidAccessKeyLength}, // Secret key too small. {"myuser", "pass", errInvalidSecretKeyLength}, // Secret key too long. @@ -49,8 +51,6 @@ func testAuthenticate(authType string, t *testing.T) { {serverCred.AccessKey, "mypassword", errAuthentication}, // Success. {serverCred.AccessKey, serverCred.SecretKey, nil}, - // Success when access key contains leading/trailing spaces. - {" " + serverCred.AccessKey + " ", serverCred.SecretKey, nil}, } // Run tests. diff --git a/cmd/lock-instrument_test.go b/cmd/lock-instrument_test.go index f06950c64..1b562b14b 100644 --- a/cmd/lock-instrument_test.go +++ b/cmd/lock-instrument_test.go @@ -653,14 +653,14 @@ func TestNsLockMapDeleteLockInfoEntryForOps(t *testing.T) { } else { t.Fatalf("Entry for %s, %s should have existed. ", param.volume, param.path) } - if globalNSMutex.counters.granted != int64(0) { - t.Errorf("Expected the count of total running locks to be %v, but got %v", int64(0), globalNSMutex.counters.granted) + if globalNSMutex.counters.granted != 0 { + t.Errorf("Expected the count of total running locks to be %v, but got %v", 0, globalNSMutex.counters.granted) } - if globalNSMutex.counters.blocked != int64(0) { - t.Errorf("Expected the count of total blocked locks to be %v, but got %v", int64(0), globalNSMutex.counters.blocked) + if globalNSMutex.counters.blocked != 0 { + t.Errorf("Expected the count of total blocked locks to be %v, but got %v", 0, globalNSMutex.counters.blocked) } - if globalNSMutex.counters.total != int64(0) { - t.Errorf("Expected the count of all locks to be %v, but got %v", int64(0), globalNSMutex.counters.total) + if globalNSMutex.counters.total != 0 { + t.Errorf("Expected the count of all locks to be %v, but got %v", 0, globalNSMutex.counters.total) } } @@ -723,13 +723,13 @@ func TestNsLockMapDeleteLockInfoEntryForVolumePath(t *testing.T) { t.Fatalf("Entry for %s, %s should have been deleted. ", param.volume, param.path) } // The lock count values should be 0. - if globalNSMutex.counters.granted != int64(0) { - t.Errorf("Expected the count of total running locks to be %v, but got %v", int64(0), globalNSMutex.counters.granted) + if globalNSMutex.counters.granted != 0 { + t.Errorf("Expected the count of total running locks to be %v, but got %v", 0, globalNSMutex.counters.granted) } - if globalNSMutex.counters.blocked != int64(0) { - t.Errorf("Expected the count of total blocked locks to be %v, but got %v", int64(0), globalNSMutex.counters.blocked) + if globalNSMutex.counters.blocked != 0 { + t.Errorf("Expected the count of total blocked locks to be %v, but got %v", 0, globalNSMutex.counters.blocked) } - if globalNSMutex.counters.total != int64(0) { - t.Errorf("Expected the count of all locks to be %v, but got %v", int64(0), globalNSMutex.counters.total) + if globalNSMutex.counters.total != 0 { + t.Errorf("Expected the count of all locks to be %v, but got %v", 0, globalNSMutex.counters.total) } } diff --git a/cmd/lock-rpc-server.go b/cmd/lock-rpc-server.go index c1761dbd7..ad86b035d 100644 --- a/cmd/lock-rpc-server.go +++ b/cmd/lock-rpc-server.go @@ -30,7 +30,7 @@ import ( const ( // Lock rpc server endpoint. - lockRPCPath = "/minio/lock" + lockRPCPath = "/lock" // Lock maintenance interval. lockMaintenanceInterval = 1 * time.Minute // 1 minute. @@ -122,8 +122,8 @@ func registerStorageLockers(mux *router.Router, lockServers []*lockServer) error if err := lockRPCServer.RegisterName("Dsync", lockServer); err != nil { return traceError(err) } - lockRouter := mux.PathPrefix(reservedBucket).Subrouter() - lockRouter.Path(path.Join("/lock", lockServer.rpcPath)).Handler(lockRPCServer) + lockRouter := mux.PathPrefix(minioReservedBucketPath).Subrouter() + lockRouter.Path(path.Join(lockRPCPath, lockServer.rpcPath)).Handler(lockRPCServer) } return nil } diff --git a/cmd/lock-rpc-server_test.go b/cmd/lock-rpc-server_test.go index 3251a37bb..90661ca9b 100644 --- a/cmd/lock-rpc-server_test.go +++ b/cmd/lock-rpc-server_test.go @@ -85,7 +85,6 @@ func TestLockRpcServerLock(t *testing.T) { ServiceEndpoint: "rpc-path", }) la.SetAuthToken(token) - la.SetRequestTime(time.Now().UTC()) // Claim a lock var result bool @@ -119,7 +118,6 @@ func TestLockRpcServerLock(t *testing.T) { ServiceEndpoint: "rpc-path", }) la2.SetAuthToken(token) - la2.SetRequestTime(time.Now().UTC()) err = locker.Lock(&la2, &result) if err != nil { @@ -143,7 +141,6 @@ func TestLockRpcServerUnlock(t *testing.T) { ServiceEndpoint: "rpc-path", }) la.SetAuthToken(token) - la.SetRequestTime(time.Now().UTC()) // First test return of error when attempting to unlock a lock that does not exist var result bool @@ -153,7 +150,6 @@ func TestLockRpcServerUnlock(t *testing.T) { } // Create lock (so that we can release) - la.SetRequestTime(time.Now().UTC()) err = locker.Lock(&la, &result) if err != nil { t.Errorf("Expected %#v, got %#v", nil, err) @@ -162,7 +158,6 @@ func TestLockRpcServerUnlock(t *testing.T) { } // Finally test successful release of lock - la.SetRequestTime(time.Now().UTC()) err = locker.Unlock(&la, &result) if err != nil { t.Errorf("Expected %#v, got %#v", nil, err) @@ -191,7 +186,6 @@ func TestLockRpcServerRLock(t *testing.T) { ServiceEndpoint: "rpc-path", }) la.SetAuthToken(token) - la.SetRequestTime(time.Now().UTC()) // Claim a lock var result bool @@ -225,7 +219,6 @@ func TestLockRpcServerRLock(t *testing.T) { ServiceEndpoint: "rpc-path", }) la2.SetAuthToken(token) - la2.SetRequestTime(time.Now().UTC()) err = locker.RLock(&la2, &result) if err != nil { @@ -249,7 +242,6 @@ func TestLockRpcServerRUnlock(t *testing.T) { ServiceEndpoint: "rpc-path", }) la.SetAuthToken(token) - la.SetRequestTime(time.Now().UTC()) // First test return of error when attempting to unlock a read-lock that does not exist var result bool @@ -259,7 +251,6 @@ func TestLockRpcServerRUnlock(t *testing.T) { } // Create first lock ... (so that we can release) - la.SetRequestTime(time.Now().UTC()) err = locker.RLock(&la, &result) if err != nil { t.Errorf("Expected %#v, got %#v", nil, err) @@ -275,7 +266,6 @@ func TestLockRpcServerRUnlock(t *testing.T) { ServiceEndpoint: "rpc-path", }) la2.SetAuthToken(token) - la2.SetRequestTime(time.Now().UTC()) // ... and create a second lock on same resource err = locker.RLock(&la2, &result) @@ -286,7 +276,6 @@ func TestLockRpcServerRUnlock(t *testing.T) { } // Test successful release of first read lock - la.SetRequestTime(time.Now().UTC()) err = locker.RUnlock(&la, &result) if err != nil { t.Errorf("Expected %#v, got %#v", nil, err) @@ -311,7 +300,6 @@ func TestLockRpcServerRUnlock(t *testing.T) { } // Finally test successful release of second (and last) read lock - la2.SetRequestTime(time.Now().UTC()) err = locker.RUnlock(&la2, &result) if err != nil { t.Errorf("Expected %#v, got %#v", nil, err) @@ -340,7 +328,6 @@ func TestLockRpcServerForceUnlock(t *testing.T) { ServiceEndpoint: "rpc-path", }) laForce.SetAuthToken(token) - laForce.SetRequestTime(time.Now().UTC()) // First test that UID should be empty var result bool @@ -351,7 +338,6 @@ func TestLockRpcServerForceUnlock(t *testing.T) { // Then test force unlock of a lock that does not exist (not returning an error) laForce.LockArgs.UID = "" - laForce.SetRequestTime(time.Now().UTC()) err = locker.ForceUnlock(&laForce, &result) if err != nil { t.Errorf("Expected no error, got %#v", err) @@ -364,7 +350,6 @@ func TestLockRpcServerForceUnlock(t *testing.T) { ServiceEndpoint: "rpc-path", }) la.SetAuthToken(token) - la.SetRequestTime(time.Now().UTC()) // Create lock ... (so that we can force unlock) err = locker.Lock(&la, &result) @@ -375,14 +360,12 @@ func TestLockRpcServerForceUnlock(t *testing.T) { } // Forcefully unlock the lock (not returning an error) - laForce.SetRequestTime(time.Now().UTC()) err = locker.ForceUnlock(&laForce, &result) if err != nil { t.Errorf("Expected no error, got %#v", err) } // Try to get lock again (should be granted) - la.SetRequestTime(time.Now().UTC()) err = locker.Lock(&la, &result) if err != nil { t.Errorf("Expected %#v, got %#v", nil, err) @@ -391,7 +374,6 @@ func TestLockRpcServerForceUnlock(t *testing.T) { } // Finally forcefully unlock the lock once again - laForce.SetRequestTime(time.Now().UTC()) err = locker.ForceUnlock(&laForce, &result) if err != nil { t.Errorf("Expected no error, got %#v", err) @@ -410,7 +392,6 @@ func TestLockRpcServerExpired(t *testing.T) { ServiceEndpoint: "rpc-path", }) la.SetAuthToken(token) - la.SetRequestTime(time.Now().UTC()) // Unknown lock at server will return expired = true var expired bool @@ -425,7 +406,6 @@ func TestLockRpcServerExpired(t *testing.T) { // Create lock (so that we can test that it is not expired) var result bool - la.SetRequestTime(time.Now().UTC()) err = locker.Lock(&la, &result) if err != nil { t.Errorf("Expected %#v, got %#v", nil, err) @@ -433,7 +413,6 @@ func TestLockRpcServerExpired(t *testing.T) { t.Errorf("Expected %#v, got %#v", true, result) } - la.SetRequestTime(time.Now().UTC()) err = locker.Expired(&la, &expired) if err != nil { t.Errorf("Expected no error, got %#v", err) diff --git a/cmd/logger-console-hook.go b/cmd/logger-console-hook.go index fdc5264e8..a437ec1ad 100644 --- a/cmd/logger-console-hook.go +++ b/cmd/logger-console-hook.go @@ -16,7 +16,12 @@ package cmd -import "github.com/Sirupsen/logrus" +import ( + "fmt" + "strings" + + "github.com/Sirupsen/logrus" +) // consoleLogger - default logger if not other logging is enabled. type consoleLogger struct { @@ -24,6 +29,14 @@ type consoleLogger struct { Level string `json:"level"` } +func (c *consoleLogger) Validate() error { + level := strings.ToLower(c.Level) + if level != "error" && level != "fatal" && level != "" { + return fmt.Errorf("`%s` level value not recognized", c.Level) + } + return nil +} + // enable console logger. func enableConsoleLogger() { clogger := serverConfig.Logger.GetConsole() diff --git a/cmd/logger-file-hook.go b/cmd/logger-file-hook.go index c7b5f6652..9fbd20ea2 100644 --- a/cmd/logger-file-hook.go +++ b/cmd/logger-file-hook.go @@ -20,6 +20,7 @@ import ( "fmt" "io/ioutil" "os" + "strings" "github.com/Sirupsen/logrus" ) @@ -30,6 +31,14 @@ type fileLogger struct { Level string `json:"level"` } +func (f *fileLogger) Validate() error { + level := strings.ToLower(f.Level) + if level != "error" && level != "fatal" && level != "" { + return fmt.Errorf("`%s` level value not recognized", f.Level) + } + return nil +} + type localFile struct { *os.File } diff --git a/cmd/logger.go b/cmd/logger.go index 4aba14543..a6ef792b8 100644 --- a/cmd/logger.go +++ b/cmd/logger.go @@ -47,6 +47,20 @@ type logger struct { /// Logger related. +// Validate logger contents +func (l *logger) Validate() error { + if l == nil { + return nil + } + if err := l.Console.Validate(); err != nil { + return fmt.Errorf("`Console` field: %s", err.Error()) + } + if err := l.File.Validate(); err != nil { + return fmt.Errorf("`File` field: %s", err.Error()) + } + return nil +} + // SetFile set new file logger. func (l *logger) SetFile(flogger fileLogger) { l.Lock() @@ -119,6 +133,7 @@ func fatalIf(err error, msg string, data ...interface{}) { if e, ok := err.(*Error); ok { fields["stack"] = strings.Join(e.Trace(), " ") } + for _, log := range log.loggers { log.WithFields(fields).Fatalf(msg, data...) } diff --git a/cmd/main.go b/cmd/main.go index 75b8d531e..659dfac27 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -1,5 +1,5 @@ /* - * Minio Cloud Storage, (C) 2015, 2016 Minio, Inc. + * Minio Cloud Storage, (C) 2015, 2016, 2017 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,29 +17,26 @@ package cmd import ( - "fmt" "os" "sort" - "time" "github.com/minio/cli" "github.com/minio/mc/pkg/console" + "github.com/minio/minio/pkg/trie" ) -var ( - // global flags for minio. - globalFlags = []cli.Flag{ - cli.StringFlag{ - Name: "config-dir, C", - Value: mustGetConfigPath(), - Usage: "Path to configuration directory.", - }, - cli.BoolFlag{ - Name: "quiet", - Usage: "Disable startup information.", - }, - } -) +// global flags for minio. +var globalFlags = []cli.Flag{ + cli.StringFlag{ + Name: "config-dir, C", + Value: getConfigDir(), + Usage: "Path to configuration directory.", + }, + cli.BoolFlag{ + Name: "quiet", + Usage: "Disable startup information.", + }, +} // Help template for minio. var minioHelpTemplate = `NAME: @@ -61,158 +58,88 @@ VERSION: ` + Version + `{{ "\n"}}` -func migrate() { - // Migrate config file - err := migrateConfig() - fatalIf(err, "Config migration failed.") +func newApp() *cli.App { + // Collection of minio commands currently supported are. + commands := []cli.Command{} - // Migrate other configs here. -} + // Collection of minio commands currently supported in a trie tree. + commandsTree := trie.NewTrie() -func enableLoggers() { - // Enable all loggers here. - enableConsoleLogger() - enableFileLogger() - // Add your logger here. -} - -func findClosestCommands(command string) []string { - var closestCommands []string - for _, value := range commandsTree.PrefixMatch(command) { - closestCommands = append(closestCommands, value.(string)) + // registerCommand registers a cli command. + registerCommand := func(command cli.Command) { + commands = append(commands, command) + commandsTree.Insert(command.Name) } - sort.Strings(closestCommands) - // Suggest other close commands - allow missed, wrongly added and - // even transposed characters - for _, value := range commandsTree.Walk(commandsTree.Root()) { - if sort.SearchStrings(closestCommands, value.(string)) < len(closestCommands) { - continue - } - // 2 is arbitrary and represents the max - // allowed number of typed errors - if DamerauLevenshteinDistance(command, value.(string)) < 2 { + + findClosestCommands := func(command string) []string { + var closestCommands []string + for _, value := range commandsTree.PrefixMatch(command) { closestCommands = append(closestCommands, value.(string)) } - } - return closestCommands -} -func registerApp() *cli.App { + sort.Strings(closestCommands) + // Suggest other close commands - allow missed, wrongly added and + // even transposed characters + for _, value := range commandsTree.Walk(commandsTree.Root()) { + if sort.SearchStrings(closestCommands, value.(string)) < len(closestCommands) { + continue + } + // 2 is arbitrary and represents the max + // allowed number of typed errors + if DamerauLevenshteinDistance(command, value.(string)) < 2 { + closestCommands = append(closestCommands, value.(string)) + } + } + + return closestCommands + } + // Register all commands. registerCommand(serverCmd) registerCommand(versionCmd) registerCommand(updateCmd) + registerCommand(gatewayCmd) // Set up app. + cli.HelpFlag = cli.BoolFlag{ + Name: "help, h", + Usage: "Show help.", + } + app := cli.NewApp() app.Name = "Minio" app.Author = "Minio.io" + app.Version = Version app.Usage = "Cloud Storage Server." app.Description = `Minio is an Amazon S3 compatible object storage server. Use it to store photos, videos, VMs, containers, log files, or any blob of data as objects.` app.Flags = globalFlags + app.HideVersion = true // Hide `--version` flag, we already have `minio version`. + app.HideHelpCommand = true // Hide `help, h` command, we already have `minio --help`. app.Commands = commands app.CustomAppHelpTemplate = minioHelpTemplate app.CommandNotFound = func(ctx *cli.Context, command string) { - msg := fmt.Sprintf("‘%s’ is not a minio sub-command. See ‘minio --help’.", command) + console.Printf("‘%s’ is not a minio sub-command. See ‘minio --help’.\n", command) closestCommands := findClosestCommands(command) if len(closestCommands) > 0 { - msg += fmt.Sprintf("\n\nDid you mean one of these?\n") + console.Println() + console.Println("Did you mean one of these?") for _, cmd := range closestCommands { - msg += fmt.Sprintf(" ‘%s’\n", cmd) + console.Printf("\t‘%s’\n", cmd) } } - console.Fatalln(msg) + + os.Exit(1) } + return app } -// Verify main command syntax. -func checkMainSyntax(c *cli.Context) { - configPath, err := getConfigPath() - if err != nil { - console.Fatalf("Unable to obtain user's home directory. \nError: %s\n", err) - } - if configPath == "" { - console.Fatalln("Config directory cannot be empty, please specify --config-dir .") - } -} - -// Check for updates and print a notification message -func checkUpdate() { - // Do not print update messages, if quiet flag is set. - if !globalQuiet { - older, downloadURL, err := getUpdateInfo(1 * time.Second) - if err != nil { - // Its OK to ignore any errors during getUpdateInfo() here. - return - } - if older > time.Duration(0) { - console.Println(colorizeUpdateMessage(downloadURL, older)) - } - } -} - -// Initializes a new config if it doesn't exist, else migrates any old config -// to newer config and finally loads the config to memory. -func initConfig() { - envCreds := mustGetCredentialFromEnv() - - // Config file does not exist, we create it fresh and return upon success. - if !isConfigFileExists() { - if err := newConfig(envCreds); err != nil { - console.Fatalf("Unable to initialize minio config for the first time. Err: %s.\n", err) - } - console.Println("Created minio configuration file successfully at " + mustGetConfigPath()) - return - } - - // Migrate any old version of config / state files to newer format. - migrate() - - // Once we have migrated all the old config, now load them. - if err := loadConfig(envCreds); err != nil { - console.Fatalf("Unable to initialize minio config. Err: %s.\n", err) - } -} - -// Generic Minio initialization to create/load config, prepare loggers, etc.. -func minioInit(ctx *cli.Context) { - // Set global variables after parsing passed arguments - setGlobalsFromContext(ctx) - - // Sets new config directory. - setGlobalConfigPath(globalConfigDir) - - // Is TLS configured?. - globalIsSSL = isSSL() - - // Initialize minio server config. - initConfig() - - // Enable all loggers by now so we can use errorIf() and fatalIf() - enableLoggers() - - // Init the error tracing module. - initError() - -} - // Main main for minio server. -func Main(args []string, exitFn func(int)) { - app := registerApp() - app.Before = func(c *cli.Context) error { - // Valid input arguments to main. - checkMainSyntax(c) - return nil - } - - // Start profiler if env is set. - if profiler := os.Getenv("_MINIO_PROFILER"); profiler != "" { - globalProfiler = startProfiler(profiler) - } +func Main(args []string) { + app := newApp() // Run the app - exit on error. if err := app.Run(args); err != nil { - exitFn(1) + os.Exit(1) } } diff --git a/cmd/namespace-lock.go b/cmd/namespace-lock.go index 492e45ac4..36bd8e71c 100644 --- a/cmd/namespace-lock.go +++ b/cmd/namespace-lock.go @@ -51,7 +51,7 @@ func initDsyncNodes(eps []*url.URL) error { accessKey: cred.AccessKey, secretKey: cred.SecretKey, serverAddr: ep.Host, - serviceEndpoint: pathutil.Join(lockRPCPath, getPath(ep)), + serviceEndpoint: pathutil.Join(minioReservedBucketPath, lockRPCPath, getPath(ep)), secureConn: globalIsSSL, serviceName: "Dsync", }) diff --git a/cmd/notifier-config.go b/cmd/notifier-config.go index 2c9728158..531b4d016 100644 --- a/cmd/notifier-config.go +++ b/cmd/notifier-config.go @@ -16,7 +16,10 @@ package cmd -import "sync" +import ( + "fmt" + "sync" +) // Notifier represents collection of supported notification queues. type notifier struct { @@ -41,6 +44,15 @@ func (a amqpConfigs) Clone() amqpConfigs { return a2 } +func (a amqpConfigs) Validate() error { + for k, v := range a { + if err := v.Validate(); err != nil { + return fmt.Errorf("AMQP [%s] configuration invalid: %s", k, err.Error()) + } + } + return nil +} + type natsConfigs map[string]natsNotify func (a natsConfigs) Clone() natsConfigs { @@ -51,6 +63,15 @@ func (a natsConfigs) Clone() natsConfigs { return a2 } +func (a natsConfigs) Validate() error { + for k, v := range a { + if err := v.Validate(); err != nil { + return fmt.Errorf("NATS [%s] configuration invalid: %s", k, err.Error()) + } + } + return nil +} + type elasticSearchConfigs map[string]elasticSearchNotify func (a elasticSearchConfigs) Clone() elasticSearchConfigs { @@ -61,6 +82,15 @@ func (a elasticSearchConfigs) Clone() elasticSearchConfigs { return a2 } +func (a elasticSearchConfigs) Validate() error { + for k, v := range a { + if err := v.Validate(); err != nil { + return fmt.Errorf("ElasticSearch [%s] configuration invalid: %s", k, err.Error()) + } + } + return nil +} + type redisConfigs map[string]redisNotify func (a redisConfigs) Clone() redisConfigs { @@ -71,6 +101,15 @@ func (a redisConfigs) Clone() redisConfigs { return a2 } +func (a redisConfigs) Validate() error { + for k, v := range a { + if err := v.Validate(); err != nil { + return fmt.Errorf("Redis [%s] configuration invalid: %s", k, err.Error()) + } + } + return nil +} + type postgreSQLConfigs map[string]postgreSQLNotify func (a postgreSQLConfigs) Clone() postgreSQLConfigs { @@ -81,6 +120,15 @@ func (a postgreSQLConfigs) Clone() postgreSQLConfigs { return a2 } +func (a postgreSQLConfigs) Validate() error { + for k, v := range a { + if err := v.Validate(); err != nil { + return fmt.Errorf("PostgreSQL [%s] configuration invalid: %s", k, err.Error()) + } + } + return nil +} + type kafkaConfigs map[string]kafkaNotify func (a kafkaConfigs) Clone() kafkaConfigs { @@ -91,6 +139,15 @@ func (a kafkaConfigs) Clone() kafkaConfigs { return a2 } +func (a kafkaConfigs) Validate() error { + for k, v := range a { + if err := v.Validate(); err != nil { + return fmt.Errorf("Kafka [%s] configuration invalid: %s", k, err.Error()) + } + } + return nil +} + type webhookConfigs map[string]webhookNotify func (a webhookConfigs) Clone() webhookConfigs { @@ -101,6 +158,43 @@ func (a webhookConfigs) Clone() webhookConfigs { return a2 } +func (a webhookConfigs) Validate() error { + for k, v := range a { + if err := v.Validate(); err != nil { + return fmt.Errorf("Webhook [%s] configuration invalid: %s", k, err.Error()) + } + } + return nil +} + +func (n *notifier) Validate() error { + if n == nil { + return nil + } + if err := n.AMQP.Validate(); err != nil { + return err + } + if err := n.NATS.Validate(); err != nil { + return err + } + if err := n.ElasticSearch.Validate(); err != nil { + return err + } + if err := n.Redis.Validate(); err != nil { + return err + } + if err := n.PostgreSQL.Validate(); err != nil { + return err + } + if err := n.Kafka.Validate(); err != nil { + return err + } + if err := n.Webhook.Validate(); err != nil { + return err + } + return nil +} + func (n *notifier) SetAMQPByID(accountID string, amqpn amqpNotify) { n.Lock() defer n.Unlock() diff --git a/cmd/notifiers.go b/cmd/notifiers.go index 1d3ed1bfd..cbc31e816 100644 --- a/cmd/notifiers.go +++ b/cmd/notifiers.go @@ -98,10 +98,7 @@ func isWebhookQueue(sqsArn arnSQS) bool { return false } rNotify := serverConfig.Notify.GetWebhookByID(sqsArn.AccountID) - if !rNotify.Enable { - return false - } - return true + return rNotify.Enable } // Returns true if queueArn is for an Redis queue. diff --git a/cmd/notify-amqp.go b/cmd/notify-amqp.go index 9168cc959..04965d954 100644 --- a/cmd/notify-amqp.go +++ b/cmd/notify-amqp.go @@ -18,6 +18,7 @@ package cmd import ( "io/ioutil" + "net" "github.com/Sirupsen/logrus" "github.com/streadway/amqp" @@ -39,6 +40,16 @@ type amqpNotify struct { AutoDeleted bool `json:"autoDeleted"` } +func (a *amqpNotify) Validate() error { + if !a.Enable { + return nil + } + if _, err := checkURL(a.URL); err != nil { + return err + } + return nil +} + type amqpConn struct { params amqpNotify *amqp.Connection @@ -87,7 +98,14 @@ func (q amqpConn) Fire(entry *logrus.Entry) error { ch, err := q.Connection.Channel() if err != nil { // Any other error other than connection closed, return. - if err != amqp.ErrClosed { + isClosedErr := false + if neterr, ok := err.(*net.OpError); ok && + neterr.Err.Error() == "use of closed network connection" { + isClosedErr = true + } else if err == amqp.ErrClosed { + isClosedErr = true + } + if !isClosedErr { return err } // Attempt to connect again. diff --git a/cmd/notify-elasticsearch.go b/cmd/notify-elasticsearch.go index 508b7ed9e..e35eef0cd 100644 --- a/cmd/notify-elasticsearch.go +++ b/cmd/notify-elasticsearch.go @@ -33,6 +33,16 @@ type elasticSearchNotify struct { Index string `json:"index"` } +func (e *elasticSearchNotify) Validate() error { + if !e.Enable { + return nil + } + if _, err := checkURL(e.URL); err != nil { + return err + } + return nil +} + type elasticClient struct { *elastic.Client params elasticSearchNotify diff --git a/cmd/notify-kafka.go b/cmd/notify-kafka.go index 7ddf454b8..3ce4112d2 100644 --- a/cmd/notify-kafka.go +++ b/cmd/notify-kafka.go @@ -17,6 +17,7 @@ package cmd import ( + "errors" "fmt" "io/ioutil" @@ -39,6 +40,16 @@ type kafkaNotify struct { Topic string `json:"topic"` } +func (k *kafkaNotify) Validate() error { + if !k.Enable { + return nil + } + if len(k.Brokers) == 0 { + return errors.New("No broker specified") + } + return nil +} + // kafkaConn contains the active connection to the Kafka cluster and // the topic to send event notifications to. type kafkaConn struct { diff --git a/cmd/notify-nats.go b/cmd/notify-nats.go index b18751ded..1659eefc2 100644 --- a/cmd/notify-nats.go +++ b/cmd/notify-nats.go @@ -48,6 +48,16 @@ type natsNotify struct { Streaming natsNotifyStreaming `json:"streaming"` } +func (n *natsNotify) Validate() error { + if !n.Enable { + return nil + } + if _, err := checkURL(n.Address); err != nil { + return err + } + return nil +} + // natsIOConn abstracts connection to any type of NATS server type natsIOConn struct { params natsNotify diff --git a/cmd/notify-postgresql.go b/cmd/notify-postgresql.go index 116092520..fadecd332 100644 --- a/cmd/notify-postgresql.go +++ b/cmd/notify-postgresql.go @@ -84,6 +84,16 @@ type postgreSQLNotify struct { Database string `json:"database"` } +func (p *postgreSQLNotify) Validate() error { + if !p.Enable { + return nil + } + if _, err := checkURL(p.Host); err != nil { + return err + } + return nil +} + type pgConn struct { connStr string table string diff --git a/cmd/notify-redis.go b/cmd/notify-redis.go index 8e382edb5..1f385d472 100644 --- a/cmd/notify-redis.go +++ b/cmd/notify-redis.go @@ -21,7 +21,7 @@ import ( "time" "github.com/Sirupsen/logrus" - "github.com/minio/redigo/redis" + "github.com/garyburd/redigo/redis" ) // redisNotify to send logs to Redis server @@ -32,6 +32,16 @@ type redisNotify struct { Key string `json:"key"` } +func (r *redisNotify) Validate() error { + if !r.Enable { + return nil + } + if _, err := checkURL(r.Addr); err != nil { + return err + } + return nil +} + type redisConn struct { *redis.Pool params redisNotify diff --git a/cmd/notify-webhook.go b/cmd/notify-webhook.go index 7bdb8f086..c64421996 100644 --- a/cmd/notify-webhook.go +++ b/cmd/notify-webhook.go @@ -32,6 +32,16 @@ type webhookNotify struct { Endpoint string `json:"endpoint"` } +func (w *webhookNotify) Validate() error { + if !w.Enable { + return nil + } + if _, err := checkURL(w.Endpoint); err != nil { + return err + } + return nil +} + type httpConn struct { *http.Client Endpoint string diff --git a/cmd/object-api-common.go b/cmd/object-api-common.go index 0f0c4a414..b2fd37af6 100644 --- a/cmd/object-api-common.go +++ b/cmd/object-api-common.go @@ -20,7 +20,6 @@ import ( "net" "net/url" "runtime" - "strings" "sync" "time" @@ -39,14 +38,14 @@ const ( ) // Global object layer mutex, used for safely updating object layer. -var globalObjLayerMutex *sync.Mutex +var globalObjLayerMutex *sync.RWMutex // Global object layer, only accessed by newObjectLayerFn(). var globalObjectAPI ObjectLayer func init() { // Initialize this once per server initialization. - globalObjLayerMutex = &sync.Mutex{} + globalObjLayerMutex = &sync.RWMutex{} } // Check if the disk is remote. @@ -59,7 +58,7 @@ func isRemoteDisk(disk StorageAPI) bool { // if size == 0 and object ends with slashSeparator then // returns true. func isObjectDir(object string, size int64) bool { - return strings.HasSuffix(object, slashSeparator) && size == 0 + return hasSuffix(object, slashSeparator) && size == 0 } // Converts just bucket, object metadata into ObjectInfo datatype. @@ -135,10 +134,7 @@ func isLocalStorage(ep *url.URL) bool { if globalMinioHost != "" && globalMinioPort != "" { // if --address host:port was specified for distXL we short // circuit only the endPoint that matches host:port - if net.JoinHostPort(globalMinioHost, globalMinioPort) == ep.Host { - return true - } - return false + return net.JoinHostPort(globalMinioHost, globalMinioPort) == ep.Host } // Split host to extract host information. host, _, err := net.SplitHostPort(ep.Host) @@ -284,7 +280,7 @@ func cleanupDir(storage StorageAPI, volume, dirPath string) error { var delFunc func(string) error // Function to delete entries recursively. delFunc = func(entryPath string) error { - if !strings.HasSuffix(entryPath, slashSeparator) { + if !hasSuffix(entryPath, slashSeparator) { // Delete the file entry. return traceError(storage.DeleteFile(volume, entryPath)) } diff --git a/cmd/object-api-datatypes.go b/cmd/object-api-datatypes.go index 307220f4d..17d8d9366 100644 --- a/cmd/object-api-datatypes.go +++ b/cmd/object-api-datatypes.go @@ -248,6 +248,8 @@ type uploadMetadata struct { Initiated time.Time StorageClass string // Not supported yet. + + HealUploadInfo *HealObjectInfo `xml:"HealUploadInfo,omitempty"` } // completePart - completed part container. diff --git a/cmd/object-api-errors.go b/cmd/object-api-errors.go index 78a62c1b2..1f9ea1561 100644 --- a/cmd/object-api-errors.go +++ b/cmd/object-api-errors.go @@ -17,7 +17,6 @@ package cmd import ( - "errors" "fmt" "io" ) @@ -252,9 +251,6 @@ func (e IncompleteBody) Error() string { return e.Bucket + "#" + e.Object + "has incomplete body" } -// errInvalidRange - returned when given range value is not valid. -var errInvalidRange = errors.New("Invalid range") - // InvalidRange - invalid range typed error. type InvalidRange struct { offsetBegin int64 diff --git a/cmd/object-api-input-checks.go b/cmd/object-api-input-checks.go index 62e77ab16..2975f117b 100644 --- a/cmd/object-api-input-checks.go +++ b/cmd/object-api-input-checks.go @@ -151,7 +151,7 @@ func checkBucketExist(bucket string, obj ObjectLayer) error { } _, err := obj.GetBucketInfo(bucket) if err != nil { - return BucketNotFound{Bucket: bucket} + return errorCause(err) } return nil } diff --git a/cmd/object-api-interface.go b/cmd/object-api-interface.go index 4938af274..ca60e728c 100644 --- a/cmd/object-api-interface.go +++ b/cmd/object-api-interface.go @@ -52,4 +52,6 @@ type ObjectLayer interface { ListBucketsHeal() (buckets []BucketInfo, err error) HealObject(bucket, object string) error ListObjectsHeal(bucket, prefix, marker, delimiter string, maxKeys int) (ListObjectsInfo, error) + ListUploadsHeal(bucket, prefix, marker, uploadIDMarker, + delimiter string, maxUploads int) (ListMultipartsInfo, error) } diff --git a/cmd/object-api-putobject_test.go b/cmd/object-api-putobject_test.go index 45c6fb516..3e889bfb9 100644 --- a/cmd/object-api-putobject_test.go +++ b/cmd/object-api-putobject_test.go @@ -146,7 +146,7 @@ func testObjectAPIPutObject(obj ObjectLayer, instanceType string, t TestErrHandl // data with size different from the actual number of bytes available in the reader {bucket, object, data, nil, "", int64(len(data) - 1), getMD5Hash(data[:len(data)-1]), nil}, {bucket, object, nilBytes, nil, "", int64(len(nilBytes) + 1), getMD5Hash(nilBytes), IncompleteBody{}}, - {bucket, object, fiveMBBytes, nil, "", int64(0), getMD5Hash(fiveMBBytes), nil}, + {bucket, object, fiveMBBytes, nil, "", 0, getMD5Hash(fiveMBBytes), nil}, // Test case 30 // valid data with X-Amz-Meta- meta @@ -179,11 +179,11 @@ func testObjectAPIPutObject(obj ObjectLayer, instanceType string, t TestErrHandl // Wrapper for calling PutObject tests for both XL multiple disks case // when quorum is not available. func TestObjectAPIPutObjectDiskNotFound(t *testing.T) { - ExecObjectLayerDiskAlteredTest(t, testObjectAPIPutObjectDiskNotFOund) + ExecObjectLayerDiskAlteredTest(t, testObjectAPIPutObjectDiskNotFound) } // Tests validate correctness of PutObject. -func testObjectAPIPutObjectDiskNotFOund(obj ObjectLayer, instanceType string, disks []string, t *testing.T) { +func testObjectAPIPutObjectDiskNotFound(obj ObjectLayer, instanceType string, disks []string, t *testing.T) { // Generating cases for which the PutObject fails. bucket := "minio-bucket" object := "minio-object" diff --git a/cmd/object-api-utils.go b/cmd/object-api-utils.go index 206c0c1a0..ce78e9265 100644 --- a/cmd/object-api-utils.go +++ b/cmd/object-api-utils.go @@ -21,7 +21,6 @@ import ( "fmt" "io" "path" - "regexp" "runtime" "strings" "unicode/utf8" @@ -38,12 +37,10 @@ const ( minioMetaMultipartBucket = minioMetaBucket + "/" + mpartMetaPrefix // Minio Tmp meta prefix. minioMetaTmpBucket = minioMetaBucket + "/tmp" + // DNS separator (period), used for bucket name validation. + dnsDelimiter = "." ) -// validBucket regexp. -var validBucket = regexp.MustCompile(`^[a-z0-9][a-z0-9\.\-]{1,61}[a-z0-9]$`) -var isIPAddress = regexp.MustCompile(`^(\d+\.){3}\d+$`) - // isMinioBucket returns true if given bucket is a Minio internal // bucket and false otherwise. func isMinioMetaBucketName(bucket string) bool { @@ -52,10 +49,13 @@ func isMinioMetaBucketName(bucket string) bool { bucket == minioMetaTmpBucket } -// IsValidBucketName verifies a bucket name in accordance with Amazon's -// requirements. It must be 3-63 characters long, can contain dashes -// and periods, but must begin and end with a lowercase letter or a number. -// See: http://docs.aws.amazon.com/AmazonS3/latest/dev/BucketRestrictions.html +// IsValidBucketName verifies that a bucket name is in accordance with +// Amazon's requirements (i.e. DNS naming conventions). It must be 3-63 +// characters long, and it must be a sequence of one or more labels +// separated by periods. Each label can contain lowercase ascii +// letters, decimal digits and hyphens, but must not begin or end with +// a hyphen. See: +// http://docs.aws.amazon.com/AmazonS3/latest/dev/BucketRestrictions.html func IsValidBucketName(bucket string) bool { // Special case when bucket is equal to one of the meta buckets. if isMinioMetaBucketName(bucket) { @@ -64,12 +64,39 @@ func IsValidBucketName(bucket string) bool { if len(bucket) < 3 || len(bucket) > 63 { return false } - if bucket[0] == '.' || bucket[len(bucket)-1] == '.' { - return false + + // Split on dot and check each piece conforms to rules. + allNumbers := true + pieces := strings.Split(bucket, dnsDelimiter) + for _, piece := range pieces { + if len(piece) == 0 || piece[0] == '-' || + piece[len(piece)-1] == '-' { + // Current piece has 0-length or starts or + // ends with a hyphen. + return false + } + // Now only need to check if each piece is a valid + // 'label' in AWS terminology and if the bucket looks + // like an IP address. + isNotNumber := false + for i := 0; i < len(piece); i++ { + switch { + case (piece[i] >= 'a' && piece[i] <= 'z' || + piece[i] == '-'): + // Found a non-digit character, so + // this piece is not a number. + isNotNumber = true + case piece[i] >= '0' && piece[i] <= '9': + // Nothing to do. + default: + // Found invalid character. + return false + } + } + allNumbers = allNumbers && !isNotNumber } - return (validBucket.MatchString(bucket) && - !isIPAddress.MatchString(bucket) && - !strings.Contains(bucket, "..")) + // Does the bucket name look like an IP address? + return !(len(pieces) == 4 && allNumbers) } // IsValidObjectName verifies an object name in accordance with Amazon's @@ -129,7 +156,7 @@ func retainSlash(s string) string { func pathJoin(elem ...string) string { trailingSlash := "" if len(elem) > 0 { - if strings.HasSuffix(elem[len(elem)-1], slashSeparator) { + if hasSuffix(elem[len(elem)-1], slashSeparator) { trailingSlash = "/" } } @@ -164,7 +191,7 @@ func getCompleteMultipartMD5(parts []completePart) (string, error) { // For example on windows since its case insensitive we are supposed // to do case insensitive checks. func hasPrefix(s string, prefix string) bool { - if runtime.GOOS == "windows" { + if runtime.GOOS == globalWindowsOSName { return strings.HasPrefix(strings.ToLower(s), strings.ToLower(prefix)) } return strings.HasPrefix(s, prefix) @@ -174,12 +201,39 @@ func hasPrefix(s string, prefix string) bool { // For example on windows since its case insensitive we are supposed // to do case insensitive checks. func hasSuffix(s string, suffix string) bool { - if runtime.GOOS == "windows" { + if runtime.GOOS == globalWindowsOSName { return strings.HasSuffix(strings.ToLower(s), strings.ToLower(suffix)) } return strings.HasSuffix(s, suffix) } +// Validates if two strings are equal. +func isStringEqual(s1 string, s2 string) bool { + if runtime.GOOS == globalWindowsOSName { + return strings.EqualFold(s1, s2) + } + return s1 == s2 +} + +// Ignores all reserved bucket names or invalid bucket names. +func isReservedOrInvalidBucket(bucketEntry string) bool { + bucketEntry = strings.TrimSuffix(bucketEntry, slashSeparator) + if !IsValidBucketName(bucketEntry) { + return true + } + return isMinioMetaBucket(bucketEntry) || isMinioReservedBucket(bucketEntry) +} + +// Returns true if input bucket is a reserved minio meta bucket '.minio.sys'. +func isMinioMetaBucket(bucketName string) bool { + return bucketName == minioMetaBucket +} + +// Returns true if input bucket is a reserved minio bucket 'minio'. +func isMinioReservedBucket(bucketName string) bool { + return bucketName == minioReservedBucket +} + // byBucketName is a collection satisfying sort.Interface. type byBucketName []BucketInfo diff --git a/cmd/object-api-utils_test.go b/cmd/object-api-utils_test.go index b93ceabb8..abf3560da 100644 --- a/cmd/object-api-utils_test.go +++ b/cmd/object-api-utils_test.go @@ -41,6 +41,8 @@ func TestIsValidBucketName(t *testing.T) { {"testbucket", true}, {"1bucket", true}, {"bucket1", true}, + {"a.b", true}, + {"ab.a.bc", true}, // cases for which test should fail. // passing invalid bucket names. {"------", false}, @@ -59,11 +61,14 @@ func TestIsValidBucketName(t *testing.T) { {"ends-with-a-dot.", false}, {"ends-with-a-dash-", false}, {"-starts-with-a-dash", false}, - {"THIS-BEINGS-WITH-UPPERCASe", false}, + {"THIS-BEGINS-WITH-UPPERCASe", false}, {"tHIS-ENDS-WITH-UPPERCASE", false}, - {"ThisBeginsAndEndsWithUpperCase", false}, + {"ThisBeginsAndEndsWithUpperCasE", false}, {"una ñina", false}, - {"lalalallalallalalalallalallalala-theString-size-is-greater-than-64", false}, + {"dash-.may-not-appear-next-to-dot", false}, + {"dash.-may-not-appear-next-to-dot", false}, + {"dash-.-may-not-appear-next-to-dot", false}, + {"lalalallalallalalalallalallalala-thestring-size-is-greater-than-63", false}, } for i, testCase := range testCases { diff --git a/cmd/object-handlers.go b/cmd/object-handlers.go index a3ef52f9f..7e423fd6c 100644 --- a/cmd/object-handlers.go +++ b/cmd/object-handlers.go @@ -25,7 +25,6 @@ import ( "path" "sort" "strconv" - "strings" mux "github.com/gorilla/mux" ) @@ -133,7 +132,7 @@ func (api objectAPIHandlers) GetObjectHandler(w http.ResponseWriter, r *http.Req } // Get the object. - startOffset := int64(0) + var startOffset int64 length := objInfo.Size if hrange != nil { startOffset = hrange.offsetBegin @@ -359,12 +358,10 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re // Notify object created event. eventNotify(eventData{ - Type: ObjectCreatedCopy, - Bucket: dstBucket, - ObjInfo: objInfo, - ReqParams: map[string]string{ - "sourceIPAddress": r.RemoteAddr, - }, + Type: ObjectCreatedCopy, + Bucket: dstBucket, + ObjInfo: objInfo, + ReqParams: extractReqParams(r), }) } @@ -421,6 +418,13 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req // Extract metadata to be saved from incoming HTTP header. metadata := extractMetadataFromHeader(r.Header) + if rAuthType == authTypeStreamingSigned { + // Make sure to delete the content-encoding parameter + // for a streaming signature which is set to value + // "aws-chunked" + delete(metadata, "content-encoding") + } + // Make sure we hex encode md5sum here. metadata["md5Sum"] = hex.EncodeToString(md5Bytes) @@ -476,7 +480,7 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req objInfo, err = objectAPI.PutObject(bucket, object, size, r.Body, metadata, sha256sum) } if err != nil { - errorIf(err, "Unable to create an object.") + errorIf(err, "Unable to create an object. %s", r.URL.Path) writeErrorResponse(w, toAPIErrorCode(err), r.URL) return } @@ -485,12 +489,10 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req // Notify object created event. eventNotify(eventData{ - Type: ObjectCreatedPut, - Bucket: bucket, - ObjInfo: objInfo, - ReqParams: map[string]string{ - "sourceIPAddress": r.RemoteAddr, - }, + Type: ObjectCreatedPut, + Bucket: bucket, + ObjInfo: objInfo, + ReqParams: extractReqParams(r), }) } @@ -594,16 +596,12 @@ func (api objectAPIHandlers) CopyObjectPartHandler(w http.ResponseWriter, r *htt var hrange *httpRange rangeHeader := r.Header.Get("x-amz-copy-source-range") if rangeHeader != "" { - if hrange, err = parseRequestRange(rangeHeader, objInfo.Size); err != nil { + if hrange, err = parseCopyPartRange(rangeHeader, objInfo.Size); err != nil { // Handle only errInvalidRange // Ignore other parse error and treat it as regular Get request like Amazon S3. - if err == errInvalidRange { - writeErrorResponse(w, ErrInvalidRange, r.URL) - return - } - - // log the error. - errorIf(err, "Invalid request range") + errorIf(err, "Unable to extract range %s", rangeHeader) + writeCopyPartErr(w, err, r.URL) + return } } @@ -613,15 +611,15 @@ func (api objectAPIHandlers) CopyObjectPartHandler(w http.ResponseWriter, r *htt } // Get the object. - startOffset := int64(0) + var startOffset int64 length := objInfo.Size if hrange != nil { - startOffset = hrange.offsetBegin length = hrange.getLength() + startOffset = hrange.offsetBegin } /// maximum copy size for multipart objects in a single operation - if isMaxObjectSize(length) { + if isMaxAllowedPartSize(length) { writeErrorResponse(w, ErrEntityTooLarge, r.URL) return } @@ -630,6 +628,7 @@ func (api objectAPIHandlers) CopyObjectPartHandler(w http.ResponseWriter, r *htt // object is same then only metadata is updated. partInfo, err := objectAPI.CopyObjectPart(srcBucket, srcObject, dstBucket, dstObject, uploadID, partID, startOffset, length) if err != nil { + errorIf(err, "Unable to perform CopyObjectPart %s/%s", srcBucket, srcObject) writeErrorResponse(w, toAPIErrorCode(err), r.URL) return } @@ -653,6 +652,12 @@ func (api objectAPIHandlers) PutObjectPartHandler(w http.ResponseWriter, r *http return } + // X-Amz-Copy-Source shouldn't be set for this call. + if _, ok := r.Header["X-Amz-Copy-Source"]; ok { + writeErrorResponse(w, ErrInvalidCopySource, r.URL) + return + } + // get Content-Md5 sent by client and verify if valid md5Bytes, err := checkValidMD5(r.Header.Get("Content-Md5")) if err != nil { @@ -680,7 +685,7 @@ func (api objectAPIHandlers) PutObjectPartHandler(w http.ResponseWriter, r *http } /// maximum Upload size for multipart objects in a single operation - if isMaxObjectSize(size) { + if isMaxAllowedPartSize(size) { writeErrorResponse(w, ErrEntityTooLarge, r.URL) return } @@ -868,8 +873,7 @@ func (api objectAPIHandlers) CompleteMultipartUploadHandler(w http.ResponseWrite // Complete parts. var completeParts []completePart for _, part := range complMultipartUpload.Parts { - part.ETag = strings.TrimPrefix(part.ETag, "\"") - part.ETag = strings.TrimSuffix(part.ETag, "\"") + part.ETag = canonicalizeETag(part.ETag) completeParts = append(completeParts, part) } @@ -912,12 +916,10 @@ func (api objectAPIHandlers) CompleteMultipartUploadHandler(w http.ResponseWrite // Notify object created event. eventNotify(eventData{ - Type: ObjectCreatedCompleteMultipartUpload, - Bucket: bucket, - ObjInfo: objInfo, - ReqParams: map[string]string{ - "sourceIPAddress": r.RemoteAddr, - }, + Type: ObjectCreatedCompleteMultipartUpload, + Bucket: bucket, + ObjInfo: objInfo, + ReqParams: extractReqParams(r), }) } @@ -960,8 +962,6 @@ func (api objectAPIHandlers) DeleteObjectHandler(w http.ResponseWriter, r *http. ObjInfo: ObjectInfo{ Name: object, }, - ReqParams: map[string]string{ - "sourceIPAddress": r.RemoteAddr, - }, + ReqParams: extractReqParams(r), }) } diff --git a/cmd/object-handlers_test.go b/cmd/object-handlers_test.go index 95e126202..feda6e29b 100644 --- a/cmd/object-handlers_test.go +++ b/cmd/object-handlers_test.go @@ -682,6 +682,13 @@ func testAPIPutObjectStreamSigV4Handler(obj ObjectLayer, instanceType, bucketNam continue } + objInfo, err := obj.GetObjectInfo(testCase.bucketName, testCase.objectName) + if err != nil { + t.Fatalf("Test %d: %s: Failed to fetch the copied object: %s", i+1, instanceType, err) + } + if objInfo.ContentEncoding == streamingContentEncoding { + t.Fatalf("Test %d: %s: ContentEncoding is set to \"aws-chunked\" which is unexpected", i+1, instanceType) + } buffer := new(bytes.Buffer) err = obj.GetObject(testCase.bucketName, testCase.objectName, 0, int64(testCase.dataLen), buffer) if err != nil { @@ -833,7 +840,7 @@ func testAPIPutObjectHandler(obj ObjectLayer, instanceType, bucketName string, a req.ContentLength = -1 req.TransferEncoding = []string{} case TooBigObject: - req.ContentLength = maxObjectSize + 1 + req.ContentLength = globalMaxObjectSize + 1 } // Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler. // Call the ServeHTTP to execute the handler,`func (api objectAPIHandlers) GetObjectHandler` handles the request. @@ -876,7 +883,7 @@ func testAPIPutObjectHandler(obj ObjectLayer, instanceType, bucketName string, a reqV2.ContentLength = -1 reqV2.TransferEncoding = []string{} case TooBigObject: - reqV2.ContentLength = maxObjectSize + 1 + reqV2.ContentLength = globalMaxObjectSize + 1 } // Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler. @@ -932,6 +939,124 @@ func testAPIPutObjectHandler(obj ObjectLayer, instanceType, bucketName string, a } +// Tests sanity of attempting to copying each parts at offsets from an existing +// file and create a new object. Also validates if the written is same as what we +// expected. +func TestAPICopyObjectPartHandlerSanity(t *testing.T) { + defer DetectTestLeak(t)() + ExecObjectLayerAPITest(t, testAPICopyObjectPartHandlerSanity, []string{"CopyObjectPart"}) +} + +func testAPICopyObjectPartHandlerSanity(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler, + credentials credential, t *testing.T) { + + objectName := "test-object" + // register event notifier. + err := initEventNotifier(obj) + if err != nil { + t.Fatalf("Initializing event notifiers failed") + } + + // set of byte data for PutObject. + // object has to be created before running tests for Copy Object. + // this is required even to assert the copied object, + bytesData := []struct { + byteData []byte + }{ + {generateBytesData(6 * humanize.MiByte)}, + } + + // set of inputs for uploading the objects before tests for downloading is done. + putObjectInputs := []struct { + bucketName string + objectName string + contentLength int64 + textData []byte + metaData map[string]string + }{ + // case - 1. + {bucketName, objectName, int64(len(bytesData[0].byteData)), bytesData[0].byteData, make(map[string]string)}, + } + sha256sum := "" + // iterate through the above set of inputs and upload the object. + for i, input := range putObjectInputs { + // uploading the object. + _, err = obj.PutObject(input.bucketName, input.objectName, input.contentLength, + bytes.NewBuffer(input.textData), input.metaData, sha256sum) + // if object upload fails stop the test. + if err != nil { + t.Fatalf("Put Object case %d: Error uploading object: %v", i+1, err) + } + } + + // Initiate Multipart upload for testing PutObjectPartHandler. + testObject := "testobject" + + // PutObjectPart API HTTP Handler has to be tested in isolation, + // that is without any other handler being registered, + // That's why NewMultipartUpload is initiated using ObjectLayer. + uploadID, err := obj.NewMultipartUpload(bucketName, testObject, nil) + if err != nil { + // Failed to create NewMultipartUpload, abort. + t.Fatalf("Minio %s : %s", instanceType, err) + } + + a := 0 + b := globalMinPartSize - 1 + var parts []completePart + for partNumber := 1; partNumber <= 2; partNumber++ { + // initialize HTTP NewRecorder, this records any mutations to response writer inside the handler. + rec := httptest.NewRecorder() + cpPartURL := getCopyObjectPartURL("", bucketName, testObject, uploadID, fmt.Sprintf("%d", partNumber)) + + // construct HTTP request for copy object. + var req *http.Request + req, err = newTestSignedRequestV4("PUT", cpPartURL, 0, nil, credentials.AccessKey, credentials.SecretKey) + if err != nil { + t.Fatalf("Test failed to create HTTP request for copy object part: %v", err) + } + + // "X-Amz-Copy-Source" header contains the information about the source bucket and the object to copied. + req.Header.Set("X-Amz-Copy-Source", url.QueryEscape(pathJoin(bucketName, objectName))) + req.Header.Set("X-Amz-Copy-Source-Range", fmt.Sprintf("bytes=%d-%d", a, b)) + + // Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler. + // Call the ServeHTTP to execute the handler, `func (api objectAPIHandlers) CopyObjectHandler` handles the request. + a = globalMinPartSize + b = len(bytesData[0].byteData) - 1 + apiRouter.ServeHTTP(rec, req) + if rec.Code != http.StatusOK { + t.Fatalf("Test failed to create HTTP request for copy %d", rec.Code) + } + + resp := &CopyObjectPartResponse{} + if err = xmlDecoder(rec.Body, resp, rec.Result().ContentLength); err != nil { + t.Fatalf("Test failed to decode XML response: %v", err) + } + + parts = append(parts, completePart{ + PartNumber: partNumber, + ETag: canonicalizeETag(resp.ETag), + }) + } + + result, err := obj.CompleteMultipartUpload(bucketName, testObject, uploadID, parts) + if err != nil { + t.Fatalf("Test: %s complete multipart upload failed: %v", instanceType, err) + } + if result.Size != int64(len(bytesData[0].byteData)) { + t.Fatalf("Test: %s expected size not written: expected %d, got %d", instanceType, len(bytesData[0].byteData), result.Size) + } + + var buf bytes.Buffer + if err = obj.GetObject(bucketName, testObject, 0, int64(len(bytesData[0].byteData)), &buf); err != nil { + t.Fatalf("Test: %s reading completed file failed: %v", instanceType, err) + } + if !bytes.Equal(buf.Bytes(), bytesData[0].byteData) { + t.Fatalf("Test: %s returned data is not expected corruption detected:", instanceType) + } +} + // Wrapper for calling Copy Object Part API handler tests for both XL multiple disks and single node setup. func TestAPICopyObjectPartHandler(t *testing.T) { defer DetectTestLeak(t)() @@ -1062,10 +1187,23 @@ func testAPICopyObjectPartHandler(obj ObjectLayer, instanceType, bucketName stri accessKey: credentials.AccessKey, secretKey: credentials.SecretKey, - expectedRespStatus: http.StatusRequestedRangeNotSatisfiable, + expectedRespStatus: http.StatusBadRequest, }, // Test case - 6. + // Test case with ivalid byte range for exceeding source size boundaries. + { + bucketName: bucketName, + uploadID: uploadID, + copySourceHeader: url.QueryEscape("/" + bucketName + "/" + objectName), + copySourceRange: "bytes=0-6144", + accessKey: credentials.AccessKey, + secretKey: credentials.SecretKey, + + expectedRespStatus: http.StatusBadRequest, + }, + + // Test case - 7. // Test case with object name missing from source. // fail with BadRequest. { @@ -1078,7 +1216,7 @@ func testAPICopyObjectPartHandler(obj ObjectLayer, instanceType, bucketName stri expectedRespStatus: http.StatusBadRequest, }, - // Test case - 7. + // Test case - 8. // Test case with non-existent source file. // Case for the purpose of failing `api.ObjectAPI.GetObjectInfo`. // Expecting the response status code to http.StatusNotFound (404). @@ -1092,7 +1230,7 @@ func testAPICopyObjectPartHandler(obj ObjectLayer, instanceType, bucketName stri expectedRespStatus: http.StatusNotFound, }, - // Test case - 8. + // Test case - 9. // Test case with non-existent source file. // Case for the purpose of failing `api.ObjectAPI.PutObjectPart`. // Expecting the response status code to http.StatusNotFound (404). @@ -1106,7 +1244,7 @@ func testAPICopyObjectPartHandler(obj ObjectLayer, instanceType, bucketName stri expectedRespStatus: http.StatusNotFound, }, - // Test case - 9. + // Test case - 10. // Case with invalid AccessKey. { bucketName: bucketName, @@ -1118,7 +1256,7 @@ func testAPICopyObjectPartHandler(obj ObjectLayer, instanceType, bucketName stri expectedRespStatus: http.StatusForbidden, }, - // Test case - 10. + // Test case - 11. // Case with non-existent upload id. { bucketName: bucketName, @@ -1129,7 +1267,7 @@ func testAPICopyObjectPartHandler(obj ObjectLayer, instanceType, bucketName stri expectedRespStatus: http.StatusNotFound, }, - // Test case - 11. + // Test case - 12. // invalid part number. { bucketName: bucketName, @@ -1140,7 +1278,7 @@ func testAPICopyObjectPartHandler(obj ObjectLayer, instanceType, bucketName stri secretKey: credentials.SecretKey, expectedRespStatus: http.StatusOK, }, - // Test case - 12. + // Test case - 13. // maximum part number. { bucketName: bucketName, @@ -2715,7 +2853,7 @@ func testAPIPutObjectPartHandler(obj ObjectLayer, instanceType, bucketName strin { objectName: testObject, reader: bytes.NewReader([]byte("hello")), - partNumber: strconv.Itoa(maxPartID + 1), + partNumber: strconv.Itoa(globalMaxPartID + 1), fault: None, accessKey: credentials.AccessKey, secretKey: credentials.SecretKey, @@ -2873,7 +3011,7 @@ func testAPIPutObjectPartHandler(obj ObjectLayer, instanceType, bucketName strin // Setting the content length to a value greater than the max allowed size of a part. // Used in test case 4. case TooBigObject: - req.ContentLength = maxObjectSize + 1 + req.ContentLength = globalMaxObjectSize + 1 // Malformed signature. // Used in test case 6. case BadSignature: diff --git a/cmd/object_api_suite_test.go b/cmd/object_api_suite_test.go index e3cb467ad..73af5b23d 100644 --- a/cmd/object_api_suite_test.go +++ b/cmd/object_api_suite_test.go @@ -243,7 +243,7 @@ func testPaging(obj ObjectLayer, instanceType string, c TestErrHandler) { if len(result.Objects) != 0 { c.Errorf("%s: Number of objects in the result different from expected value.", instanceType) } - if result.IsTruncated != false { + if result.IsTruncated { c.Errorf("%s: Expected IsTruncated to be `false`, but instead found it to be `%v`", instanceType, result.IsTruncated) } @@ -263,7 +263,7 @@ func testPaging(obj ObjectLayer, instanceType string, c TestErrHandler) { if len(result.Objects) != i+1 { c.Errorf("%s: Expected length of objects to be %d, instead found to be %d", instanceType, len(result.Objects), i+1) } - if result.IsTruncated != false { + if result.IsTruncated { c.Errorf("%s: Expected IsTruncated to be `false`, but instead found it to be `%v`", instanceType, result.IsTruncated) } } @@ -282,7 +282,7 @@ func testPaging(obj ObjectLayer, instanceType string, c TestErrHandler) { if len(result.Objects) != 5 { c.Errorf("%s: Expected length of objects to be %d, instead found to be %d", instanceType, 5, len(result.Objects)) } - if result.IsTruncated != true { + if !result.IsTruncated { c.Errorf("%s: Expected IsTruncated to be `true`, but instead found it to be `%v`", instanceType, result.IsTruncated) } } @@ -683,7 +683,7 @@ func testListObjectsTestsForNonExistantBucket(obj ObjectLayer, instanceType stri if len(result.Objects) != 0 { c.Fatalf("%s: Expected number of objects in the result to be `%d`, but instead found `%d`", instanceType, 0, len(result.Objects)) } - if result.IsTruncated != false { + if result.IsTruncated { c.Fatalf("%s: Expected IsTruncated to be `false`, but instead found it to be `%v`", instanceType, result.IsTruncated) } if err.Error() != "Bucket not found: bucket" { @@ -735,12 +735,13 @@ func (s *ObjectLayerAPISuite) TestGetDirectoryReturnsObjectNotFound(c *C) { // Tests validate that GetObject on an existing directory fails as expected. func testGetDirectoryReturnsObjectNotFound(obj ObjectLayer, instanceType string, c TestErrHandler) { - err := obj.MakeBucket("bucket") + bucketName := "bucket" + err := obj.MakeBucket(bucketName) if err != nil { c.Fatalf("%s: %s", instanceType, err) } - _, err = obj.PutObject("bucket", "dir1/dir3/object", + _, err = obj.PutObject(bucketName, "dir1/dir3/object", int64(len("The specified multipart upload does not exist. The upload ID might be invalid, or the multipart upload might have been aborted or completed.")), bytes.NewBufferString("One or more of the specified parts could not be found. The part might not have been uploaded, or the specified entity tag might not have matched the part's entity tag."), nil, "") @@ -748,41 +749,24 @@ func testGetDirectoryReturnsObjectNotFound(obj ObjectLayer, instanceType string, c.Fatalf("%s: %s", instanceType, err) } - _, err = obj.GetObjectInfo("bucket", "dir1") - if isErrObjectNotFound(err) { - err = errorCause(err) - err1 := err.(ObjectNotFound) - if err1.Bucket != "bucket" { - c.Errorf("%s: Expected the bucket name in the error message to be `%s`, but instead found `%s`", - instanceType, "bucket", err1.Bucket) - } - if err1.Object != "dir1" { - c.Errorf("%s: Expected the object name in the error message to be `%s`, but instead found `%s`", - instanceType, "dir1", err1.Object) - } - } else { - if err.Error() != "ObjectNotFound" { - c.Errorf("%s: Expected the error message to be `%s`, but instead found `%s`", instanceType, - "ObjectNotFound", err.Error()) - } - } - - _, err = obj.GetObjectInfo("bucket", "dir1/") - if isErrObjectNameInvalid(err) { - err = errorCause(err) - err1 := err.(ObjectNameInvalid) - if err1.Bucket != "bucket" { - c.Errorf("%s: Expected the bucket name in the error message to be `%s`, but instead found `%s`", - instanceType, "bucket", err1.Bucket) - } - if err1.Object != "dir1/" { - c.Errorf("%s: Expected the object name in the error message to be `%s`, but instead found `%s`", - instanceType, "dir1/", err1.Object) - } - } else { - // force a failure with a line number. - if err.Error() != "ObjectNotFound" { - c.Errorf("%s: Expected the error message to be `%s`, but instead found `%s`", instanceType, "ObjectNotFound", err.Error()) + for i, objName := range []string{"dir1", "dir1/", "dir1/dir3", "dir1/dir3/"} { + _, err = obj.GetObjectInfo(bucketName, objName) + if isErrObjectNotFound(err) { + err = errorCause(err) + err1 := err.(ObjectNotFound) + if err1.Bucket != bucketName { + c.Errorf("Test %d, %s: Expected the bucket name in the error message to be `%s`, but instead found `%s`", + i+1, instanceType, bucketName, err1.Bucket) + } + if err1.Object != objName { + c.Errorf("Test %d, %s: Expected the object name in the error message to be `%s`, but instead found `%s`", + i+1, instanceType, objName, err1.Object) + } + } else { + if err.Error() != "ObjectNotFound" { + c.Errorf("Test %d, %s: Expected the error message to be `%s`, but instead found `%s`", i+1, instanceType, + "ObjectNotFound", err.Error()) + } } } } diff --git a/cmd/posix.go b/cmd/posix.go index 89d1c77ed..377aef646 100644 --- a/cmd/posix.go +++ b/cmd/posix.go @@ -24,7 +24,6 @@ import ( slashpath "path" "path/filepath" "runtime" - "strings" "sync" "sync/atomic" "syscall" @@ -41,11 +40,9 @@ const ( // posix - implements StorageAPI interface. type posix struct { - ioErrCount int32 // ref: https://golang.org/pkg/sync/atomic/#pkg-note-BUG - diskPath string - minFreeSpace int64 - minFreeInodes int64 - pool sync.Pool + ioErrCount int32 // ref: https://golang.org/pkg/sync/atomic/#pkg-note-BUG + diskPath string + pool sync.Pool } // checkPathLength - returns error if given path name length more than 255 @@ -110,9 +107,7 @@ func newPosix(path string) (StorageAPI, error) { return nil, err } fs := &posix{ - diskPath: diskPath, - minFreeSpace: fsMinFreeSpace, - minFreeInodes: fsMinFreeInodes, + diskPath: diskPath, // 1MiB buffer pool for posix internal operations. pool: sync.Pool{ New: func() interface{} { @@ -134,9 +129,6 @@ func newPosix(path string) (StorageAPI, error) { return nil, err } } - if err = fs.checkDiskFree(); err != nil { - return nil, err - } return fs, nil } @@ -162,7 +154,7 @@ var ignoreDiskFreeOS = []string{ } // checkDiskFree verifies if disk path has sufficient minimum free disk space and files. -func (s *posix) checkDiskFree() (err error) { +func checkDiskFree(diskPath string, neededSpace int64) (err error) { // We don't validate disk space or inode utilization on windows. // Each windows calls to 'GetVolumeInformationW' takes around 3-5seconds. // And StatFS is not supported by Go for solaris and netbsd. @@ -171,14 +163,14 @@ func (s *posix) checkDiskFree() (err error) { } var di disk.Info - di, err = getDiskInfo(preparePath(s.diskPath)) + di, err = getDiskInfo(preparePath(diskPath)) if err != nil { return err } // Remove 5% from free space for cumulative disk space used for journalling, inodes etc. availableDiskSpace := float64(di.Free) * 0.95 - if int64(availableDiskSpace) <= s.minFreeSpace { + if int64(availableDiskSpace) <= fsMinFreeSpace { return errDiskFull } @@ -188,11 +180,16 @@ func (s *posix) checkDiskFree() (err error) { // total inodes are provided by the underlying filesystem. if di.Files != 0 && di.FSType != "NFS" { availableFiles := int64(di.Ffree) - if availableFiles <= s.minFreeInodes { + if availableFiles <= fsMinFreeInodes { return errDiskFull } } + // Check if we have enough space to store data + if neededSpace > int64(availableDiskSpace) { + return errDiskFull + } + // Success. return nil } @@ -319,7 +316,7 @@ func listVols(dirPath string) ([]VolInfo, error) { } var volsInfo []VolInfo for _, entry := range entries { - if !strings.HasSuffix(entry, slashSeparator) || !isValidVolname(slashpath.Clean(entry)) { + if !hasSuffix(entry, slashSeparator) || !isValidVolname(slashpath.Clean(entry)) { // Skip if entry is neither a directory not a valid volume name. continue } @@ -677,7 +674,7 @@ func (s *posix) PrepareFile(volume, path string, fileSize int64) (err error) { } // Validate if disk is indeed free. - if err = s.checkDiskFree(); err != nil { + if err = checkDiskFree(s.diskPath, fileSize); err != nil { return err } @@ -917,8 +914,8 @@ func (s *posix) RenameFile(srcVolume, srcPath, dstVolume, dstPath string) (err e } } - srcIsDir := strings.HasSuffix(srcPath, slashSeparator) - dstIsDir := strings.HasSuffix(dstPath, slashSeparator) + srcIsDir := hasSuffix(srcPath, slashSeparator) + dstIsDir := hasSuffix(dstPath, slashSeparator) // Either src and dst have to be directories or files, else return error. if !(srcIsDir && dstIsDir || !srcIsDir && !dstIsDir) { return errFileAccessDenied diff --git a/cmd/post-policy_test.go b/cmd/post-policy_test.go index 14018979a..29df94acc 100644 --- a/cmd/post-policy_test.go +++ b/cmd/post-policy_test.go @@ -24,6 +24,7 @@ import ( "mime/multipart" "net/http" "net/http/httptest" + "net/url" "testing" "time" @@ -444,7 +445,10 @@ func testPostPolicyBucketHandlerRedirect(obj ObjectLayer, instanceType string, t targetObj := keyName + "/upload.txt" // The url of success_action_redirect field - redirectURL := "http://www.google.com" + redirectURL, err := url.Parse("http://www.google.com") + if err != nil { + t.Fatal(err) + } // Register the API end points with XL/FS object layer. apiRouter := initTestAPIEndPoints(obj, []string{"PostPolicy"}) @@ -464,7 +468,7 @@ func testPostPolicyBucketHandlerRedirect(obj ObjectLayer, instanceType string, t rec := httptest.NewRecorder() dates := []interface{}{curTimePlus5Min.Format(expirationDateFormat), curTime.Format(iso8601DateFormat), curTime.Format(yyyymmdd)} - policy := `{"expiration": "%s","conditions":[["eq", "$bucket", "` + bucketName + `"], {"success_action_redirect":"` + redirectURL + `"},["starts-with", "$key", "test/"], ["eq", "$x-amz-algorithm", "AWS4-HMAC-SHA256"], ["eq", "$x-amz-date", "%s"], ["eq", "$x-amz-credential", "` + credentials.AccessKey + `/%s/us-east-1/s3/aws4_request"]]}` + policy := `{"expiration": "%s","conditions":[["eq", "$bucket", "` + bucketName + `"], {"success_action_redirect":"` + redirectURL.String() + `"},["starts-with", "$key", "test/"], ["eq", "$x-amz-algorithm", "AWS4-HMAC-SHA256"], ["eq", "$x-amz-date", "%s"], ["eq", "$x-amz-credential", "` + credentials.AccessKey + `/%s/us-east-1/s3/aws4_request"]]}` // Generate the final policy document policy = fmt.Sprintf(policy, dates...) @@ -472,7 +476,7 @@ func testPostPolicyBucketHandlerRedirect(obj ObjectLayer, instanceType string, t // Create a new POST request with success_action_redirect field specified req, perr := newPostRequestV4Generic("", bucketName, keyName, []byte("objData"), credentials.AccessKey, credentials.SecretKey, curTime, - []byte(policy), map[string]string{"success_action_redirect": redirectURL}, false, false) + []byte(policy), map[string]string{"success_action_redirect": redirectURL.String()}, false, false) if perr != nil { t.Fatalf("%s: Failed to create HTTP request for PostPolicyHandler: %v", instanceType, perr) @@ -492,8 +496,8 @@ func testPostPolicyBucketHandlerRedirect(obj ObjectLayer, instanceType string, t t.Error("Unexpected error: ", err) } - expectedLocation := fmt.Sprintf(redirectURL+"?bucket=%s&key=%s&etag=%s", - bucketName, getURLEncodedName(targetObj), getURLEncodedName("\""+info.MD5Sum+"\"")) + redirectURL.RawQuery = getRedirectPostRawQuery(info) + expectedLocation := redirectURL.String() // Check the new location url if rec.HeaderMap.Get("Location") != expectedLocation { diff --git a/cmd/postpolicyform.go b/cmd/postpolicyform.go index 799f5f818..6c90a294e 100644 --- a/cmd/postpolicyform.go +++ b/cmd/postpolicyform.go @@ -219,7 +219,7 @@ func checkPolicyCond(op string, input1, input2 string) bool { // checkPostPolicy - apply policy conditions and validate input values. // (http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-HTTPPOSTConstructPolicy.html) -func checkPostPolicy(formValues map[string]string, postPolicyForm PostPolicyForm) APIErrorCode { +func checkPostPolicy(formValues http.Header, postPolicyForm PostPolicyForm) APIErrorCode { // Check if policy document expiry date is still not reached if !postPolicyForm.Expiration.After(time.Now().UTC()) { return ErrPolicyAlreadyExpired @@ -242,12 +242,12 @@ func checkPostPolicy(formValues map[string]string, postPolicyForm PostPolicyForm return ErrAccessDenied } // Check if current policy condition is satisfied - condPassed = checkPolicyCond(op, formValues[formCanonicalName], v.Value) + condPassed = checkPolicyCond(op, formValues.Get(formCanonicalName), v.Value) } else { // This covers all conditions X-Amz-Meta-* and X-Amz-* if strings.HasPrefix(cond, "$x-amz-meta-") || strings.HasPrefix(cond, "$x-amz-") { // Check if policy condition is satisfied - condPassed = checkPolicyCond(op, formValues[formCanonicalName], v.Value) + condPassed = checkPolicyCond(op, formValues.Get(formCanonicalName), v.Value) } } // Check if current policy condition is satisfied, quit immediately otherwise diff --git a/cmd/postpolicyform_test.go b/cmd/postpolicyform_test.go index 11bb712d4..d2c35c43f 100644 --- a/cmd/postpolicyform_test.go +++ b/cmd/postpolicyform_test.go @@ -18,12 +18,12 @@ package cmd import ( "encoding/base64" + "net/http" "testing" ) // Test Post Policy parsing and checking conditions func TestPostPolicyForm(t *testing.T) { - type testCase struct { Bucket string Key string @@ -60,18 +60,18 @@ func TestPostPolicyForm(t *testing.T) { } // Validate all the test cases. for i, tt := range testCases { - formValues := make(map[string]string) - formValues["Bucket"] = tt.Bucket - formValues["Acl"] = tt.ACL - formValues["Key"] = tt.Key - formValues["X-Amz-Date"] = tt.XAmzDate - formValues["X-Amz-Meta-Uuid"] = tt.XAmzMetaUUID - formValues["X-Amz-Server-Side-Encryption"] = tt.XAmzServerSideEncryption - formValues["X-Amz-Algorithm"] = tt.XAmzAlgorithm - formValues["X-Amz-Credential"] = tt.XAmzCredential - formValues["Content-Type"] = tt.ContentType - formValues["Policy"] = tt.Policy - formValues["Success_action_redirect"] = tt.SuccessActionRedirect + formValues := make(http.Header) + formValues.Set("Bucket", tt.Bucket) + formValues.Set("Acl", tt.ACL) + formValues.Set("Key", tt.Key) + formValues.Set("X-Amz-Date", tt.XAmzDate) + formValues.Set("X-Amz-Meta-Uuid", tt.XAmzMetaUUID) + formValues.Set("X-Amz-Server-Side-Encryption", tt.XAmzServerSideEncryption) + formValues.Set("X-Amz-Algorithm", tt.XAmzAlgorithm) + formValues.Set("X-Amz-Credential", tt.XAmzCredential) + formValues.Set("Content-Type", tt.ContentType) + formValues.Set("Policy", tt.Policy) + formValues.Set("Success_action_redirect", tt.SuccessActionRedirect) policyBytes, err := base64.StdEncoding.DecodeString(tt.Policy) if err != nil { t.Fatal(err) diff --git a/cmd/prepare-storage-msg.go b/cmd/prepare-storage-msg.go index 445bbefca..58e4d70e1 100644 --- a/cmd/prepare-storage-msg.go +++ b/cmd/prepare-storage-msg.go @@ -46,9 +46,7 @@ func printOnceFn() printOnceFunc { var once sync.Once return func(msg string) { once.Do(func() { - if !globalQuiet { - console.Println(msg) - } + console.Println(msg) }) } } diff --git a/cmd/retry.go b/cmd/retry.go index 5e42182c6..1624b0e2d 100644 --- a/cmd/retry.go +++ b/cmd/retry.go @@ -1,5 +1,5 @@ /* - * Minio Cloud Storage, (C) 2016 Minio, Inc. + * Minio Cloud Storage, (C) 2016, 2017 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -122,7 +122,7 @@ func newRetryTimerWithJitter(unit time.Duration, cap time.Duration, jitter float } // Default retry constants. -var ( +const ( defaultRetryUnit = time.Second // 1 second. defaultRetryCap = 30 * time.Second // 30 seconds. ) diff --git a/cmd/routers.go b/cmd/routers.go index 9213f65be..8d386e4fb 100644 --- a/cmd/routers.go +++ b/cmd/routers.go @@ -22,10 +22,11 @@ import ( router "github.com/gorilla/mux" ) -func newObjectLayerFn() ObjectLayer { - globalObjLayerMutex.Lock() - defer globalObjLayerMutex.Unlock() - return globalObjectAPI +func newObjectLayerFn() (layer ObjectLayer) { + globalObjLayerMutex.RLock() + layer = globalObjectAPI + globalObjLayerMutex.RUnlock() + return } // Composed function registering routers for only distributed XL setup. diff --git a/cmd/rpc-common.go b/cmd/rpc-common.go index eeaa5e0c3..876f335d6 100644 --- a/cmd/rpc-common.go +++ b/cmd/rpc-common.go @@ -37,10 +37,6 @@ func isRequestTimeAllowed(requestTime time.Time) bool { type AuthRPCArgs struct { // Authentication token to be verified by the server for every RPC call. AuthToken string - - // Request time to be verified by the server for every RPC call. - // This is an addition check over Authentication token for time drifting. - RequestTime time.Time } // SetAuthToken - sets the token to the supplied value. @@ -48,11 +44,6 @@ func (args *AuthRPCArgs) SetAuthToken(authToken string) { args.AuthToken = authToken } -// SetRequestTime - sets the requestTime to the supplied value. -func (args *AuthRPCArgs) SetRequestTime(requestTime time.Time) { - args.RequestTime = requestTime -} - // IsAuthenticated - validated whether this auth RPC args are already authenticated or not. func (args AuthRPCArgs) IsAuthenticated() error { // Check whether the token is valid @@ -60,11 +51,6 @@ func (args AuthRPCArgs) IsAuthenticated() error { return errInvalidToken } - // Check if the request time is within the allowed skew limit. - if !isRequestTimeAllowed(args.RequestTime) { - return errServerTimeMismatch - } - // Good to go. return nil } diff --git a/cmd/s3-peer-client.go b/cmd/s3-peer-client.go index 6358f5881..a90022eb4 100644 --- a/cmd/s3-peer-client.go +++ b/cmd/s3-peer-client.go @@ -66,7 +66,7 @@ func makeS3Peers(eps []*url.URL) s3Peers { accessKey: serverCred.AccessKey, secretKey: serverCred.SecretKey, serverAddr: ep.Host, - serviceEndpoint: path.Join(reservedBucket, s3Path), + serviceEndpoint: path.Join(minioReservedBucketPath, s3Path), secureConn: globalIsSSL, serviceName: "S3", } diff --git a/cmd/s3-peer-router.go b/cmd/s3-peer-router.go index e843c17bb..3701bc8ab 100644 --- a/cmd/s3-peer-router.go +++ b/cmd/s3-peer-router.go @@ -45,7 +45,7 @@ func registerS3PeerRPCRouter(mux *router.Router) error { return traceError(err) } - s3PeerRouter := mux.NewRoute().PathPrefix(reservedBucket).Subrouter() + s3PeerRouter := mux.NewRoute().PathPrefix(minioReservedBucketPath).Subrouter() s3PeerRouter.Path(s3Path).Handler(s3PeerRPCServer) return nil } diff --git a/cmd/s3-peer-rpc-handlers_test.go b/cmd/s3-peer-rpc-handlers_test.go index a9e813cb7..825539940 100644 --- a/cmd/s3-peer-rpc-handlers_test.go +++ b/cmd/s3-peer-rpc-handlers_test.go @@ -20,7 +20,6 @@ import ( "encoding/json" "path" "testing" - "time" ) type TestRPCS3PeerSuite struct { @@ -36,7 +35,7 @@ func (s *TestRPCS3PeerSuite) SetUpSuite(t *testing.T) { serverAddr: s.testServer.Server.Listener.Addr().String(), accessKey: s.testServer.AccessKey, secretKey: s.testServer.SecretKey, - serviceEndpoint: path.Join(reservedBucket, s3Path), + serviceEndpoint: path.Join(minioReservedBucketPath, s3Path), serviceName: "S3", } } @@ -62,7 +61,7 @@ func TestS3PeerRPC(t *testing.T) { // Test S3 RPC handlers func (s *TestRPCS3PeerSuite) testS3PeerRPC(t *testing.T) { // Validate for invalid token. - args := AuthRPCArgs{AuthToken: "garbage", RequestTime: time.Now().UTC()} + args := AuthRPCArgs{AuthToken: "garbage"} rclient := newRPCClient(s.testAuthConf.serverAddr, s.testAuthConf.serviceEndpoint, false) defer rclient.Close() err := rclient.Call("S3.SetBucketNotificationPeer", &args, &AuthRPCReply{}) diff --git a/cmd/server-main.go b/cmd/server-main.go index b4e063696..a8293cc0f 100644 --- a/cmd/server-main.go +++ b/cmd/server-main.go @@ -21,6 +21,7 @@ import ( "fmt" "net" "net/url" + "os" "path" "sort" "strconv" @@ -30,13 +31,14 @@ import ( "runtime" "github.com/minio/cli" + "github.com/minio/mc/pkg/console" ) var serverFlags = []cli.Flag{ cli.StringFlag{ Name: "address", Value: ":9000", - Usage: `Bind to a specific IP:PORT. Defaults to ":9000".`, + Usage: "Bind to a specific ADDRESS:PORT, ADDRESS can be an IP or hostname.", }, } @@ -46,10 +48,10 @@ var serverCmd = cli.Command{ Flags: append(serverFlags, globalFlags...), Action: serverMain, CustomHelpTemplate: `NAME: - {{.HelpName}} - {{.Usage}} + {{.HelpName}} - {{.Usage}} USAGE: - {{.HelpName}} {{if .VisibleFlags}}[FLAGS] {{end}}PATH [PATH...] + {{.HelpName}} {{if .VisibleFlags}}[FLAGS] {{end}}PATH [PATH...] {{if .VisibleFlags}} FLAGS: {{range .VisibleFlags}}{{.}} @@ -66,7 +68,7 @@ EXAMPLES: 1. Start minio server on "/home/shared" directory. $ {{.HelpName}} /home/shared - 2. Start minio server bound to a specific IP:PORT. + 2. Start minio server bound to a specific ADDRESS:PORT. $ {{.HelpName}} --address 192.168.1.101:9000 /home/shared 3. Start erasure coded minio server on a 12 disks server. @@ -79,10 +81,114 @@ EXAMPLES: $ export MINIO_SECRET_KEY=miniostorage $ {{.HelpName}} http://192.168.1.11/mnt/export/ http://192.168.1.12/mnt/export/ \ http://192.168.1.13/mnt/export/ http://192.168.1.14/mnt/export/ - `, } +// Check for updates and print a notification message +func checkUpdate(mode string) { + // Its OK to ignore any errors during getUpdateInfo() here. + if older, downloadURL, err := getUpdateInfo(1*time.Second, mode); err == nil { + if older > time.Duration(0) { + console.Println(colorizeUpdateMessage(downloadURL, older)) + } + } +} + +// envParams holds all env parameters +type envParams struct { + creds credential + browser string +} + +func migrate() { + // Migrate config file + err := migrateConfig() + fatalIf(err, "Config migration failed.") + + // Migrate other configs here. +} + +func enableLoggers() { + // Enable all loggers here. + enableConsoleLogger() + enableFileLogger() + // Add your logger here. +} + +// Initializes a new config if it doesn't exist, else migrates any old config +// to newer config and finally loads the config to memory. +func initConfig() { + accessKey := os.Getenv("MINIO_ACCESS_KEY") + secretKey := os.Getenv("MINIO_SECRET_KEY") + + var cred credential + var err error + if accessKey != "" && secretKey != "" { + if cred, err = createCredential(accessKey, secretKey); err != nil { + console.Fatalf("Invalid access/secret Key set in environment. Err: %s.\n", err) + } + + // credential Envs are set globally. + globalIsEnvCreds = true + } + + browser := os.Getenv("MINIO_BROWSER") + if browser != "" { + if !(strings.EqualFold(browser, "off") || strings.EqualFold(browser, "on")) { + console.Fatalf("Invalid value ‘%s’ in MINIO_BROWSER environment variable.", browser) + } + + // browser Envs are set globally, this doesn't represent + // if browser is turned off or on. + globalIsEnvBrowser = true + } + + envs := envParams{ + creds: cred, + browser: browser, + } + + // Config file does not exist, we create it fresh and return upon success. + if !isConfigFileExists() { + if err := newConfig(envs); err != nil { + console.Fatalf("Unable to initialize minio config for the first time. Error: %s.\n", err) + } + console.Println("Created minio configuration file successfully at " + getConfigDir()) + return + } + + // Migrate any old version of config / state files to newer format. + migrate() + + // Validate config file + if err := validateConfig(); err != nil { + console.Fatalf("Cannot validate configuration file. Error: %s\n", err) + } + + // Once we have migrated all the old config, now load them. + if err := loadConfig(envs); err != nil { + console.Fatalf("Unable to initialize minio config. Error: %s.\n", err) + } +} + +// Generic Minio initialization to create/load config, prepare loggers, etc.. +func minioInit(ctx *cli.Context) { + // Create certs path. + fatalIf(createConfigDir(), "Unable to create \"certs\" directory.") + + // Is TLS configured?. + globalIsSSL = isSSL() + + // Initialize minio server config. + initConfig() + + // Enable all loggers by now so we can use errorIf() and fatalIf() + enableLoggers() + + // Init the error tracing module. + initError() +} + type serverCmdConfig struct { serverAddr string endpoints []*url.URL @@ -133,22 +239,11 @@ func initServerConfig(c *cli.Context) { // Initialization such as config generating/loading config, enable logging, .. minioInit(c) - // Create certs path. - fatalIf(createCertsPath(), "Unable to create \"certs\" directory.") - // Load user supplied root CAs - loadRootCAs() + fatalIf(loadRootCAs(), "Unable to load a CA files") - // Set maxOpenFiles, This is necessary since default operating - // system limits of 1024, 2048 are not enough for Minio server. - setMaxOpenFiles() - - // Set maxMemory, This is necessary since default operating - // system limits might be changed and we need to make sure we - // do not crash the server so the set the maxCacheSize appropriately. - setMaxMemory() - - // Do not fail if this is not allowed, lower limits are fine as well. + // Set system resources to maximum. + errorIf(setMaxResources(), "Unable to change resource limit") } // Validate if input disks are sufficient for initializing XL. @@ -367,12 +462,29 @@ func serverMain(c *cli.Context) { cli.ShowCommandHelpAndExit(c, "server", 1) } + // Get quiet flag from command line argument. + quietFlag := c.Bool("quiet") || c.GlobalBool("quiet") + + // Get configuration directory from command line argument. + configDir := c.String("config-dir") + if !c.IsSet("config-dir") && c.GlobalIsSet("config-dir") { + configDir = c.GlobalString("config-dir") + } + if configDir == "" { + console.Fatalln("Configuration directory cannot be empty.") + } + + // Set configuration directory. + setConfigDir(configDir) + + // Start profiler if env is set. + if profiler := os.Getenv("_MINIO_PROFILER"); profiler != "" { + globalProfiler = startProfiler(profiler) + } + // Initializes server config, certs, logging and system settings. initServerConfig(c) - // Check for new updates from dl.minio.io. - checkUpdate() - // Server address. serverAddr := c.String("address") @@ -421,6 +533,18 @@ func serverMain(c *cli.Context) { globalIsXL = true } + if !quietFlag { + // Check for new updates from dl.minio.io. + mode := globalMinioModeFS + if globalIsXL { + mode = globalMinioModeXL + } + if globalIsDistXL { + mode = globalMinioModeDistXL + } + checkUpdate(mode) + } + // Initialize name space lock. initNSLock(globalIsDistXL) @@ -451,7 +575,7 @@ func serverMain(c *cli.Context) { go func() { cert, key := "", "" if globalIsSSL { - cert, key = mustGetCertFile(), mustGetKeyFile() + cert, key = getPublicCertFile(), getPrivateKeyFile() } fatalIf(apiServer.ListenAndServe(cert, key), "Failed to start minio server.") }() @@ -467,7 +591,9 @@ func serverMain(c *cli.Context) { globalObjLayerMutex.Unlock() // Prints the formatted startup message once object layer is initialized. - printStartupMessage(apiEndPoints) + if !quietFlag { + printStartupMessage(apiEndPoints) + } // Set uptime time after object layer has initialized. globalBootTime = time.Now().UTC() diff --git a/cmd/server-main_test.go b/cmd/server-main_test.go index 84416bb62..dde1aaad6 100644 --- a/cmd/server-main_test.go +++ b/cmd/server-main_test.go @@ -367,7 +367,7 @@ func TestCheckServerSyntax(t *testing.T) { app.Commands = []cli.Command{serverCmd} serverFlagSet := flag.NewFlagSet("server", 0) serverFlagSet.String("address", ":9000", "") - ctx := cli.NewContext(app, serverFlagSet, serverFlagSet) + ctx := cli.NewContext(app, serverFlagSet, nil) disksGen := func(n int) []string { disks, err := getRandomDisks(n) @@ -458,7 +458,7 @@ func TestInitServer(t *testing.T) { app.Commands = []cli.Command{serverCmd} serverFlagSet := flag.NewFlagSet("server", 0) serverFlagSet.String("address", ":9000", "") - ctx := cli.NewContext(app, serverFlagSet, serverFlagSet) + ctx := cli.NewContext(app, serverFlagSet, nil) root, err := newTestConfig(globalMinioDefaultRegion) if err != nil { diff --git a/cmd/server-mux.go b/cmd/server-mux.go index 8ea33a3d5..3ef812a5e 100644 --- a/cmd/server-mux.go +++ b/cmd/server-mux.go @@ -26,9 +26,14 @@ import ( "net/url" "strings" "sync" + "sync/atomic" "time" ) +const ( + serverShutdownPoll = 500 * time.Millisecond +) + // The value chosen below is longest word chosen // from all the http verbs comprising of // "PRI", "OPTIONS", "GET", "HEAD", "POST", @@ -324,11 +329,13 @@ type ServerMux struct { handler http.Handler listeners []*ListenerMux - gracefulWait *sync.WaitGroup + // Current number of concurrent http requests + currentReqs int32 + // Time to wait before forcing server shutdown gracefulTimeout time.Duration - mu sync.Mutex // guards closed, and listener - closed bool + mu sync.RWMutex // guards closing, and listeners + closing bool } // NewServerMux constructor to create a ServerMux @@ -339,7 +346,6 @@ func NewServerMux(addr string, handler http.Handler) *ServerMux { // Wait for 5 seconds for new incoming connnections, otherwise // forcibly close them during graceful stop or restart. gracefulTimeout: 5 * time.Second, - gracefulWait: &sync.WaitGroup{}, } // Returns configured HTTP server. @@ -452,11 +458,22 @@ func (m *ServerMux) ListenAndServe(certFile, keyFile string) (err error) { } http.Redirect(w, r, u.String(), http.StatusTemporaryRedirect) } else { - // Execute registered handlers, protect with a waitgroup - // to accomplish a graceful shutdown when the user asks to quit - m.gracefulWait.Add(1) + + // Return ServiceUnavailable for clients which are sending requests + // in shutdown phase + m.mu.RLock() + closing := m.closing + m.mu.RUnlock() + if closing { + w.WriteHeader(http.StatusServiceUnavailable) + return + } + + // Execute registered handlers, update currentReqs to keep + // tracks of current requests currently processed by the server + atomic.AddInt32(&m.currentReqs, 1) m.handler.ServeHTTP(w, r) - m.gracefulWait.Done() + atomic.AddInt32(&m.currentReqs, -1) } }) @@ -481,12 +498,12 @@ func (m *ServerMux) ListenAndServe(certFile, keyFile string) (err error) { func (m *ServerMux) Close() error { m.mu.Lock() - if m.closed { + if m.closing { m.mu.Unlock() return errors.New("Server has been closed") } // Closed completely. - m.closed = true + m.closing = true // Close the listeners. for _, listener := range m.listeners { @@ -497,19 +514,18 @@ func (m *ServerMux) Close() error { } m.mu.Unlock() - // Prepare for a graceful shutdown - waitSignal := make(chan struct{}) - go func() { - defer close(waitSignal) - m.gracefulWait.Wait() - }() - - select { - // Wait for everything to be properly closed - case <-waitSignal: - // Forced shutdown - case <-time.After(m.gracefulTimeout): + // Starting graceful shutdown. Check if all requests are finished + // in regular interval or force the shutdown + ticker := time.NewTicker(serverShutdownPoll) + defer ticker.Stop() + for { + select { + case <-time.After(m.gracefulTimeout): + return nil + case <-ticker.C: + if atomic.LoadInt32(&m.currentReqs) <= 0 { + return nil + } + } } - - return nil } diff --git a/cmd/server-mux_test.go b/cmd/server-mux_test.go index 1a24473a6..81985c174 100644 --- a/cmd/server-mux_test.go +++ b/cmd/server-mux_test.go @@ -198,21 +198,27 @@ func TestServerMux(t *testing.T) { time.Sleep(1 * time.Second) // Check if one listener is ready m.mu.Lock() - if len(m.listeners) == 0 { - m.mu.Unlock() + listenersCount := len(m.listeners) + m.mu.Unlock() + if listenersCount == 0 { continue } + m.mu.Lock() + listenerAddr := m.listeners[0].Addr().String() m.mu.Unlock() // Issue the GET request client := http.Client{} - m.mu.Lock() - res, err = client.Get("http://" + m.listeners[0].Addr().String()) - m.mu.Unlock() + res, err = client.Get("http://" + listenerAddr) if err != nil { continue } // Read the request response got, err = ioutil.ReadAll(res.Body) + if err != nil { + continue + } + // We've got a response, quit the loop + break } // Check for error persisted after 5 times @@ -348,12 +354,12 @@ func TestServerListenAndServeTLS(t *testing.T) { })) // Create a cert - err := createCertsPath() + err := createConfigDir() if err != nil { t.Fatal(err) } - certFile := mustGetCertFile() - keyFile := mustGetKeyFile() + certFile := getPublicCertFile() + keyFile := getPrivateKeyFile() defer os.RemoveAll(certFile) defer os.RemoveAll(keyFile) @@ -414,8 +420,8 @@ func TestServerListenAndServeTLS(t *testing.T) { // generateTestCert creates a cert and a key used for testing only func generateTestCert(host string) error { - certPath := mustGetCertFile() - keyPath := mustGetKeyFile() + certPath := getPublicCertFile() + keyPath := getPrivateKeyFile() priv, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { return err diff --git a/cmd/server-rlimit-nix.go b/cmd/server-rlimit-nix.go deleted file mode 100644 index 03341a6b1..000000000 --- a/cmd/server-rlimit-nix.go +++ /dev/null @@ -1,83 +0,0 @@ -// +build !windows,!plan9,!openbsd - -/* - * Minio Cloud Storage, (C) 2016 Minio, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cmd - -import ( - "syscall" - - "github.com/minio/minio/pkg/sys" -) - -// For all unixes we need to bump allowed number of open files to a -// higher value than its usual default of '1024'. The reasoning is -// that this value is too small for a server. -func setMaxOpenFiles() error { - var rLimit syscall.Rlimit - err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit) - if err != nil { - return err - } - // Set the current limit to Max, it is usually around 4096. - // TO increase this limit further user has to manually edit - // `/etc/security/limits.conf` - rLimit.Cur = rLimit.Max - return syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit) -} - -// Set max memory used by minio as a process, this value is usually -// set to 'unlimited' but we need to validate additionally to verify -// if any hard limit is set by the user, in such a scenario would need -// to reset the global max cache size to be 80% of the hardlimit set -// by the user. This is done to honor the system limits and not crash. -func setMaxMemory() error { - var rLimit syscall.Rlimit - err := syscall.Getrlimit(syscall.RLIMIT_AS, &rLimit) - if err != nil { - return err - } - // Set the current limit to Max, it is default 'unlimited'. - // TO decrease this limit further user has to manually edit - // `/etc/security/limits.conf` - rLimit.Cur = rLimit.Max - err = syscall.Setrlimit(syscall.RLIMIT_AS, &rLimit) - if err != nil { - return err - } - err = syscall.Getrlimit(syscall.RLIMIT_AS, &rLimit) - if err != nil { - return err - } - // Validate if rlimit memory is set to lower - // than max cache size. Then we should use such value. - if uint64(rLimit.Cur) < globalMaxCacheSize { - globalMaxCacheSize = uint64(float64(50*rLimit.Cur) / 100) - } - - // Make sure globalMaxCacheSize is less than RAM size. - stats, err := sys.GetStats() - if err != nil && err != sys.ErrNotImplemented { - return err - } - // If TotalRAM is >= minRAMSize we proceed to enable cache. - // cache is always 50% of the totalRAM. - if err == nil && stats.TotalRAM >= minRAMSize { - globalMaxCacheSize = uint64(float64(50*stats.TotalRAM) / 100) - } - return nil -} diff --git a/cmd/server-rlimit-openbsd.go b/cmd/server-rlimit-openbsd.go deleted file mode 100644 index 16030447e..000000000 --- a/cmd/server-rlimit-openbsd.go +++ /dev/null @@ -1,83 +0,0 @@ -// +build openbsd - -/* - * Minio Cloud Storage, (C) 2017 Minio, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cmd - -import ( - "syscall" - - "github.com/minio/minio/pkg/sys" -) - -// For all unixes we need to bump allowed number of open files to a -// higher value than its usual default of '1024'. The reasoning is -// that this value is too small for a server. -func setMaxOpenFiles() error { - var rLimit syscall.Rlimit - err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit) - if err != nil { - return err - } - // Set the current limit to Max, it is usually around 4096. - // TO increase this limit further user has to manually edit - // `/etc/security/limits.conf` - rLimit.Cur = rLimit.Max - return syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit) -} - -// Set max memory used by minio as a process, this value is usually -// set to 'unlimited' but we need to validate additionally to verify -// if any hard limit is set by the user, in such a scenario would need -// to reset the global max cache size to be 80% of the hardlimit set -// by the user. This is done to honor the system limits and not crash. -func setMaxMemory() error { - var rLimit syscall.Rlimit - err := syscall.Getrlimit(syscall.RLIMIT_DATA, &rLimit) - if err != nil { - return err - } - // Set the current limit to Max, it is default 'unlimited'. - // TO decrease this limit further user has to manually edit - // `/etc/security/limits.conf` - rLimit.Cur = rLimit.Max - err = syscall.Setrlimit(syscall.RLIMIT_DATA, &rLimit) - if err != nil { - return err - } - err = syscall.Getrlimit(syscall.RLIMIT_DATA, &rLimit) - if err != nil { - return err - } - // Validate if rlimit memory is set to lower - // than max cache size. Then we should use such value. - if uint64(rLimit.Cur) < globalMaxCacheSize { - globalMaxCacheSize = uint64(float64(50*rLimit.Cur) / 100) - } - - // Make sure globalMaxCacheSize is less than RAM size. - stats, err := sys.GetStats() - if err != nil && err != sys.ErrNotImplemented { - return err - } - // If TotalRAM is >= minRAMSize we proceed to enable cache. - // cache is always 50% of the totalRAM. - if err == nil && stats.TotalRAM >= minRAMSize { - globalMaxCacheSize = uint64(float64(50*stats.TotalRAM) / 100) - } - return nil -} diff --git a/cmd/server-rlimit-win.go b/cmd/server-rlimit-win.go deleted file mode 100644 index 954e0f734..000000000 --- a/cmd/server-rlimit-win.go +++ /dev/null @@ -1,42 +0,0 @@ -// +build windows - -/* - * Minio Cloud Storage, (C) 2016 Minio, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cmd - -import "github.com/minio/minio/pkg/sys" - -func setMaxOpenFiles() error { - // Golang uses Win32 file API (CreateFile, WriteFile, ReadFile, - // CloseHandle, etc.), then you don't have a limit on open files - // (well, you do but it is based on your resources like memory). - return nil -} - -func setMaxMemory() error { - // Make sure globalMaxCacheSize is less than RAM size. - stats, err := sys.GetStats() - if err != nil && err != sys.ErrNotImplemented { - return err - } - // If TotalRAM is <= minRAMSize we proceed to enable cache. - // cache is always 50% of the totalRAM. - if err == nil && stats.TotalRAM >= minRAMSize { - globalMaxCacheSize = uint64(float64(50*stats.TotalRAM) / 100) - } - return nil -} diff --git a/cmd/server-rlimit.go b/cmd/server-rlimit.go new file mode 100644 index 000000000..16a1db4f7 --- /dev/null +++ b/cmd/server-rlimit.go @@ -0,0 +1,79 @@ +/* + * Minio Cloud Storage, (C) 2017 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cmd + +import "github.com/minio/minio/pkg/sys" + +func setMaxResources() (err error) { + var maxLimit uint64 + + // Set open files limit to maximum. + if _, maxLimit, err = sys.GetMaxOpenFileLimit(); err != nil { + return err + } + + if err = sys.SetMaxOpenFileLimit(maxLimit, maxLimit); err != nil { + return err + } + + // Set max memory limit as current memory limit. + if _, maxLimit, err = sys.GetMaxMemoryLimit(); err != nil { + return err + } + + err = sys.SetMaxMemoryLimit(maxLimit, maxLimit) + return err +} + +func getMaxCacheSize(curLimit, totalRAM uint64) (cacheSize uint64) { + // Return zero if current limit or totalTAM is less than minRAMSize. + if curLimit < minRAMSize || totalRAM < minRAMSize { + return cacheSize + } + + // Return 50% of current rlimit or total RAM as cache size. + if curLimit < totalRAM { + cacheSize = curLimit / 2 + } else { + cacheSize = totalRAM / 2 + } + + return cacheSize +} + +// GetMaxCacheSize returns maximum cache size based on current RAM size and memory limit. +func GetMaxCacheSize() (cacheSize uint64, err error) { + // Get max memory limit + var curLimit uint64 + if curLimit, _, err = sys.GetMaxMemoryLimit(); err != nil { + return cacheSize, err + } + + // Get total RAM. + var stats sys.Stats + if stats, err = sys.GetStats(); err != nil { + return cacheSize, err + } + + // In some OS like windows, maxLimit is zero. Set total RAM as maxLimit. + if curLimit == 0 { + curLimit = stats.TotalRAM + } + + cacheSize = getMaxCacheSize(curLimit, stats.TotalRAM) + return cacheSize, err +} diff --git a/cmd/server-rlimit_test.go b/cmd/server-rlimit_test.go new file mode 100644 index 000000000..57b9d83e4 --- /dev/null +++ b/cmd/server-rlimit_test.go @@ -0,0 +1,43 @@ +/* + * Minio Cloud Storage, (C) 2017 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cmd + +import "testing" + +func TestGetMaxCacheSize(t *testing.T) { + testCases := []struct { + curLimit uint64 + totalRAM uint64 + expectedResult uint64 + }{ + {uint64(0), uint64(0), uint64(0)}, + {minRAMSize, uint64(0), uint64(0)}, + {uint64(0), minRAMSize, uint64(0)}, + {uint64(18446744073709551615), uint64(8115998720), uint64(0)}, + {uint64(8115998720), uint64(16115998720), uint64(0)}, + {minRAMSize, minRAMSize, uint64(4294967296)}, + {minRAMSize, uint64(16115998720), uint64(4294967296)}, + {uint64(18446744073709551615), uint64(10115998720), uint64(5057999360)}, + } + + for _, testCase := range testCases { + cacheSize := getMaxCacheSize(testCase.curLimit, testCase.totalRAM) + if testCase.expectedResult != cacheSize { + t.Fatalf("expected: %v, got: %v", testCase.expectedResult, cacheSize) + } + } +} diff --git a/cmd/server-startup-msg.go b/cmd/server-startup-msg.go index 53718aa74..a51ca7ceb 100644 --- a/cmd/server-startup-msg.go +++ b/cmd/server-startup-msg.go @@ -1,5 +1,5 @@ /* - * Minio Cloud Storage, (C) 2016, 2017, 2017 Minio, Inc. + * Minio Cloud Storage, (C) 2016, 2017 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -44,11 +44,6 @@ func getFormatStr(strLen int, padding int) string { // Prints the formatted startup message. func printStartupMessage(apiEndPoints []string) { - // If quiet flag is set do not print startup message. - if globalQuiet { - return - } - // Prints credential, region and browser access. printServerCommonMsg(apiEndPoints) @@ -180,4 +175,5 @@ func getCertificateChainMsg(certs []*x509.Certificate) string { // Prints the certificate expiry message. func printCertificateMsg(certs []*x509.Certificate) { console.Println(getCertificateChainMsg(certs)) + } diff --git a/cmd/server_test.go b/cmd/server_test.go index edfdcafa0..3dffd14b2 100644 --- a/cmd/server_test.go +++ b/cmd/server_test.go @@ -94,7 +94,7 @@ func (s *TestSuiteCommon) TearDownSuite(c *C) { } func (s *TestSuiteCommon) TestAuth(c *C) { - cred := newCredential() + cred := mustGetNewCredential() c.Assert(len(cred.AccessKey), Equals, accessKeyMaxLen) c.Assert(len(cred.SecretKey), Equals, secretKeyMaxLen) @@ -2579,10 +2579,10 @@ func (s *TestSuiteCommon) TestObjectMultipart(c *C) { c.Assert(response.StatusCode, Equals, http.StatusOK) var parts []completePart for _, part := range completeUploads.Parts { - part.ETag = strings.Trim(part.ETag, "\"") + part.ETag = canonicalizeETag(part.ETag) parts = append(parts, part) } etag, err := getCompleteMultipartMD5(parts) c.Assert(err, IsNil) - c.Assert(strings.Trim(response.Header.Get("Etag"), "\""), Equals, etag) + c.Assert(canonicalizeETag(response.Header.Get("Etag")), Equals, etag) } diff --git a/cmd/server_utils_test.go b/cmd/server_utils_test.go index 7036cfd4d..a500ad772 100644 --- a/cmd/server_utils_test.go +++ b/cmd/server_utils_test.go @@ -91,7 +91,7 @@ func calculateStreamContentLength(dataLen, chunkSize int64) int64 { } chunksCount := int64(dataLen / chunkSize) remainingBytes := int64(dataLen % chunkSize) - streamLen := int64(0) + var streamLen int64 streamLen += chunksCount * calculateSignedChunkLength(chunkSize) if remainingBytes > 0 { streamLen += calculateSignedChunkLength(remainingBytes) diff --git a/cmd/signature-v2.go b/cmd/signature-v2.go index 107dbe8c5..e1eecbc90 100644 --- a/cmd/signature-v2.go +++ b/cmd/signature-v2.go @@ -64,14 +64,14 @@ var resourceList = []string{ "website", } -func doesPolicySignatureV2Match(formValues map[string]string) APIErrorCode { +func doesPolicySignatureV2Match(formValues http.Header) APIErrorCode { cred := serverConfig.GetCredential() - accessKey := formValues["Awsaccesskeyid"] + accessKey := formValues.Get("AWSAccessKeyId") if accessKey != cred.AccessKey { return ErrInvalidAccessKeyID } - signature := formValues["Signature"] - policy := formValues["Policy"] + policy := formValues.Get("Policy") + signature := formValues.Get("Signature") if signature != calculateSignatureV2(policy, cred.SecretKey) { return ErrSignatureDoesNotMatch } diff --git a/cmd/signature-v2_test.go b/cmd/signature-v2_test.go index fc5b2dee9..3a2b778aa 100644 --- a/cmd/signature-v2_test.go +++ b/cmd/signature-v2_test.go @@ -208,10 +208,10 @@ func TestDoesPolicySignatureV2Match(t *testing.T) { {creds.AccessKey, policy, calculateSignatureV2(policy, creds.SecretKey), ErrNone}, } for i, test := range testCases { - formValues := make(map[string]string) - formValues["Awsaccesskeyid"] = test.accessKey - formValues["Signature"] = test.signature - formValues["Policy"] = test.policy + formValues := make(http.Header) + formValues.Set("Awsaccesskeyid", test.accessKey) + formValues.Set("Signature", test.signature) + formValues.Set("Policy", test.policy) errCode := doesPolicySignatureV2Match(formValues) if errCode != test.errCode { t.Fatalf("(%d) expected to get %s, instead got %s", i+1, niceError(test.errCode), niceError(errCode)) diff --git a/cmd/signature-v4-parser_test.go b/cmd/signature-v4-parser_test.go index d30b8bee1..3639c093d 100644 --- a/cmd/signature-v4-parser_test.go +++ b/cmd/signature-v4-parser_test.go @@ -569,7 +569,7 @@ func TestDoesV4PresignParamsExist(t *testing.T) { func TestParsePreSignV4(t *testing.T) { // converts the duration in seconds into string format. getDurationStr := func(expires int) string { - return strconv.FormatInt(int64(expires), 10) + return strconv.Itoa(expires) } // used in expected preSignValues, preSignValues.Date is of type time.Time . queryTime := time.Now().UTC() diff --git a/cmd/signature-v4.go b/cmd/signature-v4.go index f6a5ea2fb..d32d22435 100644 --- a/cmd/signature-v4.go +++ b/cmd/signature-v4.go @@ -147,9 +147,9 @@ func getSignature(signingKey []byte, stringToSign string) string { } // Check to see if Policy is signed correctly. -func doesPolicySignatureMatch(formValues map[string]string) APIErrorCode { +func doesPolicySignatureMatch(formValues http.Header) APIErrorCode { // For SignV2 - Signature field will be valid - if formValues["Signature"] != "" { + if _, ok := formValues["Signature"]; ok { return doesPolicySignatureV2Match(formValues) } return doesPolicySignatureV4Match(formValues) @@ -158,7 +158,7 @@ func doesPolicySignatureMatch(formValues map[string]string) APIErrorCode { // doesPolicySignatureMatch - Verify query headers with post policy // - http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-HTTPPOSTConstructPolicy.html // returns ErrNone if the signature matches. -func doesPolicySignatureV4Match(formValues map[string]string) APIErrorCode { +func doesPolicySignatureV4Match(formValues http.Header) APIErrorCode { // Access credentials. cred := serverConfig.GetCredential() @@ -166,7 +166,7 @@ func doesPolicySignatureV4Match(formValues map[string]string) APIErrorCode { region := serverConfig.GetRegion() // Parse credential tag. - credHeader, err := parseCredentialHeader("Credential=" + formValues["X-Amz-Credential"]) + credHeader, err := parseCredentialHeader("Credential=" + formValues.Get("X-Amz-Credential")) if err != ErrNone { return ErrMissingFields } @@ -186,12 +186,14 @@ func doesPolicySignatureV4Match(formValues map[string]string) APIErrorCode { signingKey := getSigningKey(cred.SecretKey, credHeader.scope.date, region) // Get signature. - newSignature := getSignature(signingKey, formValues["Policy"]) + newSignature := getSignature(signingKey, formValues.Get("Policy")) // Verify signature. - if newSignature != formValues["X-Amz-Signature"] { + if newSignature != formValues.Get("X-Amz-Signature") { return ErrSignatureDoesNotMatch } + + // Success. return ErrNone } diff --git a/cmd/signature-v4_test.go b/cmd/signature-v4_test.go index 12137d1e1..41422671d 100644 --- a/cmd/signature-v4_test.go +++ b/cmd/signature-v4_test.go @@ -39,45 +39,50 @@ func TestDoesPolicySignatureMatch(t *testing.T) { accessKey := serverConfig.GetCredential().AccessKey testCases := []struct { - form map[string]string + form http.Header expected APIErrorCode }{ // (0) It should fail if 'X-Amz-Credential' is missing. { - form: map[string]string{}, + form: http.Header{}, expected: ErrMissingFields, }, // (1) It should fail if the access key is incorrect. { - form: map[string]string{ - "X-Amz-Credential": fmt.Sprintf(credentialTemplate, "EXAMPLEINVALIDEXAMPL", now.Format(yyyymmdd), globalMinioDefaultRegion), + form: http.Header{ + "X-Amz-Credential": []string{fmt.Sprintf(credentialTemplate, "EXAMPLEINVALIDEXAMPL", now.Format(yyyymmdd), globalMinioDefaultRegion)}, }, expected: ErrInvalidAccessKeyID, }, // (2) It should fail if the region is invalid. { - form: map[string]string{ - "X-Amz-Credential": fmt.Sprintf(credentialTemplate, accessKey, now.Format(yyyymmdd), "invalidregion"), + form: http.Header{ + "X-Amz-Credential": []string{fmt.Sprintf(credentialTemplate, accessKey, now.Format(yyyymmdd), "invalidregion")}, }, expected: ErrInvalidRegion, }, // (3) It should fail with a bad signature. { - form: map[string]string{ - "X-Amz-Credential": fmt.Sprintf(credentialTemplate, accessKey, now.Format(yyyymmdd), globalMinioDefaultRegion), - "X-Amz-Date": now.Format(iso8601Format), - "X-Amz-Signature": "invalidsignature", - "Policy": "policy", + form: http.Header{ + "X-Amz-Credential": []string{fmt.Sprintf(credentialTemplate, accessKey, now.Format(yyyymmdd), globalMinioDefaultRegion)}, + "X-Amz-Date": []string{now.Format(iso8601Format)}, + "X-Amz-Signature": []string{"invalidsignature"}, + "Policy": []string{"policy"}, }, expected: ErrSignatureDoesNotMatch, }, // (4) It should succeed if everything is correct. { - form: map[string]string{ - "X-Amz-Credential": fmt.Sprintf(credentialTemplate, accessKey, now.Format(yyyymmdd), globalMinioDefaultRegion), - "X-Amz-Date": now.Format(iso8601Format), - "X-Amz-Signature": getSignature(getSigningKey(serverConfig.GetCredential().SecretKey, now, globalMinioDefaultRegion), "policy"), - "Policy": "policy", + form: http.Header{ + "X-Amz-Credential": []string{ + fmt.Sprintf(credentialTemplate, accessKey, now.Format(yyyymmdd), globalMinioDefaultRegion), + }, + "X-Amz-Date": []string{now.Format(iso8601Format)}, + "X-Amz-Signature": []string{ + getSignature(getSigningKey(serverConfig.GetCredential().SecretKey, now, + globalMinioDefaultRegion), "policy"), + }, + "Policy": []string{"policy"}, }, expected: ErrNone, }, diff --git a/cmd/storage-rpc-client.go b/cmd/storage-rpc-client.go index 4020b6382..af90744cd 100644 --- a/cmd/storage-rpc-client.go +++ b/cmd/storage-rpc-client.go @@ -34,7 +34,7 @@ type networkStorage struct { } const ( - storageRPCPath = reservedBucket + "/storage" + storageRPCPath = "/storage" ) // Converts rpc.ServerError to underlying error. This function is @@ -102,7 +102,7 @@ func newStorageRPC(ep *url.URL) (StorageAPI, error) { } // Dial minio rpc storage http path. - rpcPath := path.Join(storageRPCPath, getPath(ep)) + rpcPath := path.Join(minioReservedBucketPath, storageRPCPath, getPath(ep)) rpcAddr := ep.Host serverCred := serverConfig.GetCredential() diff --git a/cmd/storage-rpc-server.go b/cmd/storage-rpc-server.go index 6ad287868..1a02cd266 100644 --- a/cmd/storage-rpc-server.go +++ b/cmd/storage-rpc-server.go @@ -233,8 +233,8 @@ func registerStorageRPCRouters(mux *router.Router, srvCmdConfig serverCmdConfig) return traceError(err) } // Add minio storage routes. - storageRouter := mux.PathPrefix(reservedBucket).Subrouter() - storageRouter.Path(path.Join("/storage", stServer.path)).Handler(storageRPCServer) + storageRouter := mux.PathPrefix(minioReservedBucketPath).Subrouter() + storageRouter.Path(path.Join(storageRPCPath, stServer.path)).Handler(storageRPCServer) } return nil } diff --git a/cmd/storage-rpc-server_test.go b/cmd/storage-rpc-server_test.go index 48082e8ad..9b5e001fa 100644 --- a/cmd/storage-rpc-server_test.go +++ b/cmd/storage-rpc-server_test.go @@ -98,33 +98,28 @@ func TestStorageRPCInvalidToken(t *testing.T) { } // 1. DiskInfoHandler diskInfoReply := &disk.Info{} - badAuthRPCArgs.RequestTime = time.Now().UTC() err = storageRPC.DiskInfoHandler(&badAuthRPCArgs, diskInfoReply) errorIfInvalidToken(t, err) // 2. MakeVolHandler makeVolArgs := &badGenericVolArgs - makeVolArgs.AuthRPCArgs.RequestTime = time.Now().UTC() makeVolReply := &AuthRPCReply{} err = storageRPC.MakeVolHandler(makeVolArgs, makeVolReply) errorIfInvalidToken(t, err) // 3. ListVolsHandler listVolReply := &ListVolsReply{} - badAuthRPCArgs.RequestTime = time.Now().UTC() err = storageRPC.ListVolsHandler(&badAuthRPCArgs, listVolReply) errorIfInvalidToken(t, err) // 4. StatVolHandler statVolReply := &VolInfo{} statVolArgs := &badGenericVolArgs - statVolArgs.AuthRPCArgs.RequestTime = time.Now().UTC() err = storageRPC.StatVolHandler(statVolArgs, statVolReply) errorIfInvalidToken(t, err) // 5. DeleteVolHandler deleteVolArgs := &badGenericVolArgs - deleteVolArgs.AuthRPCArgs.RequestTime = time.Now().UTC() deleteVolReply := &AuthRPCReply{} err = storageRPC.DeleteVolHandler(deleteVolArgs, deleteVolReply) errorIfInvalidToken(t, err) @@ -133,7 +128,6 @@ func TestStorageRPCInvalidToken(t *testing.T) { statFileArgs := &StatFileArgs{ AuthRPCArgs: badAuthRPCArgs, } - statFileArgs.AuthRPCArgs.RequestTime = time.Now().UTC() statReply := &FileInfo{} err = storageRPC.StatFileHandler(statFileArgs, statReply) errorIfInvalidToken(t, err) @@ -142,7 +136,6 @@ func TestStorageRPCInvalidToken(t *testing.T) { listDirArgs := &ListDirArgs{ AuthRPCArgs: badAuthRPCArgs, } - listDirArgs.AuthRPCArgs.RequestTime = time.Now().UTC() listDirReply := &[]string{} err = storageRPC.ListDirHandler(listDirArgs, listDirReply) errorIfInvalidToken(t, err) @@ -151,13 +144,11 @@ func TestStorageRPCInvalidToken(t *testing.T) { readFileArgs := &ReadFileArgs{ AuthRPCArgs: badAuthRPCArgs, } - readFileArgs.AuthRPCArgs.RequestTime = time.Now().UTC() readFileReply := &[]byte{} err = storageRPC.ReadAllHandler(readFileArgs, readFileReply) errorIfInvalidToken(t, err) // 9. ReadFileHandler - readFileArgs.AuthRPCArgs.RequestTime = time.Now().UTC() err = storageRPC.ReadFileHandler(readFileArgs, readFileReply) errorIfInvalidToken(t, err) @@ -165,7 +156,6 @@ func TestStorageRPCInvalidToken(t *testing.T) { prepFileArgs := &PrepareFileArgs{ AuthRPCArgs: badAuthRPCArgs, } - prepFileArgs.AuthRPCArgs.RequestTime = time.Now().UTC() prepFileReply := &AuthRPCReply{} err = storageRPC.PrepareFileHandler(prepFileArgs, prepFileReply) errorIfInvalidToken(t, err) @@ -174,7 +164,6 @@ func TestStorageRPCInvalidToken(t *testing.T) { appendArgs := &AppendFileArgs{ AuthRPCArgs: badAuthRPCArgs, } - appendArgs.AuthRPCArgs.RequestTime = time.Now().UTC() appendReply := &AuthRPCReply{} err = storageRPC.AppendFileHandler(appendArgs, appendReply) errorIfInvalidToken(t, err) @@ -183,7 +172,6 @@ func TestStorageRPCInvalidToken(t *testing.T) { delFileArgs := &DeleteFileArgs{ AuthRPCArgs: badAuthRPCArgs, } - delFileArgs.AuthRPCArgs.RequestTime = time.Now().UTC() delFileRely := &AuthRPCReply{} err = storageRPC.DeleteFileHandler(delFileArgs, delFileRely) errorIfInvalidToken(t, err) @@ -192,7 +180,6 @@ func TestStorageRPCInvalidToken(t *testing.T) { renameArgs := &RenameFileArgs{ AuthRPCArgs: badAuthRPCArgs, } - renameArgs.AuthRPCArgs.RequestTime = time.Now().UTC() renameReply := &AuthRPCReply{} err = storageRPC.RenameFileHandler(renameArgs, renameReply) errorIfInvalidToken(t, err) diff --git a/cmd/streaming-signature-v4.go b/cmd/streaming-signature-v4.go index 9383c17a0..4fc4eca21 100644 --- a/cmd/streaming-signature-v4.go +++ b/cmd/streaming-signature-v4.go @@ -34,9 +34,10 @@ import ( // Streaming AWS Signature Version '4' constants. const ( - emptySHA256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" - streamingContentSHA256 = "STREAMING-AWS4-HMAC-SHA256-PAYLOAD" - signV4ChunkedAlgorithm = "AWS4-HMAC-SHA256-PAYLOAD" + emptySHA256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + streamingContentSHA256 = "STREAMING-AWS4-HMAC-SHA256-PAYLOAD" + signV4ChunkedAlgorithm = "AWS4-HMAC-SHA256-PAYLOAD" + streamingContentEncoding = "aws-chunked" ) // getChunkSignature - get chunk signature. diff --git a/cmd/test-utils_test.go b/cmd/test-utils_test.go index 7ef2f25b2..efaa6dd7d 100644 --- a/cmd/test-utils_test.go +++ b/cmd/test-utils_test.go @@ -65,8 +65,8 @@ func init() { // Disable printing console messages during tests. color.Output = ioutil.Discard - // Enable caching. - setMaxMemory() + // Set system resources to maximum. + setMaxResources() } func prepareFS() (ObjectLayer, string, error) { @@ -75,19 +75,11 @@ func prepareFS() (ObjectLayer, string, error) { if err != nil { return nil, "", err } - endpoints, err := parseStorageEndpoints(fsDirs) + obj, err := newFSObjectLayer(fsDirs[0]) if err != nil { return nil, "", err } - fsPath, err := url.QueryUnescape(endpoints[0].String()) - if err != nil { - return nil, "", err - } - obj, err := newFSObjectLayer(fsPath) - if err != nil { - return nil, "", err - } - return obj, endpoints[0].Path, nil + return obj, fsDirs[0], nil } func prepareXL() (ObjectLayer, []string, error) { @@ -193,16 +185,18 @@ type TestServer struct { SrvCmdCfg serverCmdConfig } +// UnstartedTestServer - Configures a temp FS/XL backend, +// initializes the endpoints and configures the test server. +// The server should be started using the Start() method. func UnstartedTestServer(t TestErrHandler, instanceType string) TestServer { // create an instance of TestServer. testServer := TestServer{} - // create temporary backend for the test server. - nDisks := 16 - disks, err := getRandomDisks(nDisks) + // return FS/XL object layer and temp backend. + objLayer, disks, err := prepareTestBackend(instanceType) if err != nil { - t.Fatal("Failed to create disks for the backend") + t.Fatal(err) } - + // set the server configuration. root, err := newTestConfig(globalMinioDefaultRegion) if err != nil { t.Fatalf("%s", err) @@ -212,19 +206,15 @@ func UnstartedTestServer(t TestErrHandler, instanceType string) TestServer { // Get credential. credentials := serverConfig.GetCredential() - testServer.Root = root + testServer.Obj = objLayer testServer.Disks, err = parseStorageEndpoints(disks) if err != nil { - t.Fatalf("Unexpected error %s", err) + t.Fatalf("Unexpected error %v", err) } + testServer.Root = root testServer.AccessKey = credentials.AccessKey testServer.SecretKey = credentials.SecretKey - objLayer, _, err := initObjectLayer(testServer.Disks) - if err != nil { - t.Fatalf("Failed obtaining Temp Backend: %s", err) - } - srvCmdCfg := serverCmdConfig{ endpoints: testServer.Disks, } @@ -238,10 +228,9 @@ func UnstartedTestServer(t TestErrHandler, instanceType string) TestServer { // Run TestServer. testServer.Server = httptest.NewUnstartedServer(httpHandler) - + // obtain server address. srvCmdCfg.serverAddr = testServer.Server.Listener.Addr().String() - testServer.Obj = objLayer globalObjLayerMutex.Lock() globalObjectAPI = objLayer globalObjLayerMutex.Unlock() @@ -451,7 +440,7 @@ func StartTestPeersRPCServer(t TestErrHandler, instanceType string) TestServer { // Sets the global config path to empty string. func resetGlobalConfigPath() { - setGlobalConfigPath("") + setConfigDir("") } // sets globalObjectAPI to `nil`. @@ -530,10 +519,10 @@ func newTestConfig(bucketLocation string) (rootPath string, err error) { } // Do this only once here. - setGlobalConfigPath(rootPath) + setConfigDir(rootPath) // Initialize server config. - if err = newConfig(credential{}); err != nil { + if err = newConfig(envParams{}); err != nil { return "", err } @@ -1701,9 +1690,7 @@ func removeDiskN(disks []string, n int) { // Makes a entire new copy of a StorageAPI slice. func deepCopyStorageDisks(storageDisks []StorageAPI) []StorageAPI { newStorageDisks := make([]StorageAPI, len(storageDisks)) - for i, disk := range storageDisks { - newStorageDisks[i] = disk - } + copy(newStorageDisks, storageDisks) return newStorageDisks } @@ -1774,6 +1761,24 @@ func initAPIHandlerTest(obj ObjectLayer, endpoints []string) (string, http.Handl return bucketName, f, nil } +// prepare test backend. +// create FS/XL bankend. +// return object layer, backend disks. +func prepareTestBackend(instanceType string) (ObjectLayer, []string, error) { + switch instanceType { + // Total number of disks for XL backend is set to 16. + case XLTestStr: + return prepareXL() + default: + // return FS backend by default. + obj, disk, err := prepareFS() + if err != nil { + return nil, nil, err + } + return obj, []string{disk}, nil + } +} + // ExecObjectLayerAPIAnonTest - Helper function to validate object Layer API handler // response for anonymous/unsigned and unknown signature type HTTP request. diff --git a/cmd/tree-walk_test.go b/cmd/tree-walk_test.go index 9490c6c56..211630d05 100644 --- a/cmd/tree-walk_test.go +++ b/cmd/tree-walk_test.go @@ -21,7 +21,6 @@ import ( "io/ioutil" "reflect" "sort" - "strings" "testing" "time" ) @@ -187,7 +186,7 @@ func TestTreeWalk(t *testing.T) { } isLeaf := func(volume, prefix string) bool { - return !strings.HasSuffix(prefix, slashSeparator) + return !hasSuffix(prefix, slashSeparator) } listDir := listDirFactory(isLeaf, xlTreeWalkIgnoredErrs, disk) // Simple test for prefix based walk. @@ -225,7 +224,7 @@ func TestTreeWalkTimeout(t *testing.T) { } isLeaf := func(volume, prefix string) bool { - return !strings.HasSuffix(prefix, slashSeparator) + return !hasSuffix(prefix, slashSeparator) } listDir := listDirFactory(isLeaf, xlTreeWalkIgnoredErrs, disk) @@ -304,7 +303,7 @@ func TestListDir(t *testing.T) { // create listDir function. listDir := listDirFactory(func(volume, prefix string) bool { - return !strings.HasSuffix(prefix, slashSeparator) + return !hasSuffix(prefix, slashSeparator) }, xlTreeWalkIgnoredErrs, disk1, disk2) // Create file1 in fsDir1 and file2 in fsDir2. @@ -376,7 +375,7 @@ func TestRecursiveTreeWalk(t *testing.T) { // Simple isLeaf check, returns true if there is no trailing "/" isLeaf := func(volume, prefix string) bool { - return !strings.HasSuffix(prefix, slashSeparator) + return !hasSuffix(prefix, slashSeparator) } // Create listDir function. @@ -486,7 +485,7 @@ func TestSortedness(t *testing.T) { // Simple isLeaf check, returns true if there is no trailing "/" isLeaf := func(volume, prefix string) bool { - return !strings.HasSuffix(prefix, slashSeparator) + return !hasSuffix(prefix, slashSeparator) } // Create listDir function. listDir := listDirFactory(isLeaf, xlTreeWalkIgnoredErrs, disk1) @@ -563,7 +562,7 @@ func TestTreeWalkIsEnd(t *testing.T) { } isLeaf := func(volume, prefix string) bool { - return !strings.HasSuffix(prefix, slashSeparator) + return !hasSuffix(prefix, slashSeparator) } // Create listDir function. listDir := listDirFactory(isLeaf, xlTreeWalkIgnoredErrs, disk1) diff --git a/cmd/typed-errors.go b/cmd/typed-errors.go index 5a7568568..ae77a611b 100644 --- a/cmd/typed-errors.go +++ b/cmd/typed-errors.go @@ -50,3 +50,14 @@ var errServerVersionMismatch = errors.New("Server versions do not match") // errServerTimeMismatch - server times are too far apart. var errServerTimeMismatch = errors.New("Server times are too far apart") + +// errReservedBucket - bucket name is reserved for Minio, usually +// returned for 'minio', '.minio.sys' +var errReservedBucket = errors.New("All access to this bucket is disabled") + +// errInvalidRange - returned when given range value is not valid. +var errInvalidRange = errors.New("Invalid range") + +// errInvalidRangeSource - returned when given range value exceeds +// the source object size. +var errInvalidRangeSource = errors.New("Range specified exceeds source object size") diff --git a/cmd/update-main.go b/cmd/update-main.go index 23721fb2b..d41bed83f 100644 --- a/cmd/update-main.go +++ b/cmd/update-main.go @@ -22,6 +22,8 @@ import ( "io/ioutil" "net/http" "os" + "os/exec" + "path/filepath" "runtime" "strings" "time" @@ -46,7 +48,7 @@ var updateCmd = cli.Command{ {{.HelpName}} - {{.Usage}} USAGE: - {{.HelpName}} {{if .VisibleFlags}}[FLAGS]{{end}} + {{.HelpName}}{{if .VisibleFlags}} [FLAGS]{{end}} {{if .VisibleFlags}} FLAGS: {{range .VisibleFlags}}{{.}} @@ -56,19 +58,30 @@ EXIT STATUS: 1 - New update is available. -1 - Error in getting update information. -VERSION: - ` + Version + `{{"\n"}}`, +EXAMPLES: + 1. Check if there is a new update available: + $ {{.HelpName}} +`, } -const releaseTagTimeLayout = "2006-01-02T15-04-05Z" - -const minioReleaseURL = "https://dl.minio.io/server/minio/release/" + runtime.GOOS + "-" + runtime.GOARCH + "/" +const ( + minioReleaseTagTimeLayout = "2006-01-02T15-04-05Z" + minioReleaseURL = "https://dl.minio.io/server/minio/release/" + runtime.GOOS + "-" + runtime.GOARCH + "/" +) func getCurrentReleaseTime(minioVersion, minioBinaryPath string) (releaseTime time.Time, err error) { if releaseTime, err = time.Parse(time.RFC3339, minioVersion); err == nil { return releaseTime, err } + if !filepath.IsAbs(minioBinaryPath) { + // Make sure to look for the absolute path of the binary. + minioBinaryPath, err = exec.LookPath(minioBinaryPath) + if err != nil { + return releaseTime, err + } + } + // Looks like version is minio non-standard, we use minio binary's ModTime as release time. fi, err := os.Stat(minioBinaryPath) if err != nil { @@ -120,8 +133,11 @@ func IsSourceBuild() bool { // Minio (; [; docker][; source]) Minio/ Minio/ Minio/ // // For any change here should be discussed by openning an issue at https://github.com/minio/minio/issues. -func getUserAgent() string { +func getUserAgent(mode string) string { userAgent := "Minio (" + runtime.GOOS + "; " + runtime.GOARCH + if mode != "" { + userAgent += "; " + mode + } if IsDocker() { userAgent += "; docker" } @@ -133,15 +149,19 @@ func getUserAgent() string { return userAgent } -func downloadReleaseData(releaseChecksumURL string, timeout time.Duration) (data string, err error) { +func downloadReleaseData(releaseChecksumURL string, timeout time.Duration, mode string) (data string, err error) { req, err := http.NewRequest("GET", releaseChecksumURL, nil) if err != nil { return data, err } - req.Header.Set("User-Agent", getUserAgent()) + req.Header.Set("User-Agent", getUserAgent(mode)) client := &http.Client{ Timeout: timeout, + Transport: &http.Transport{ + // need to close connection after usage. + DisableKeepAlives: true, + }, } resp, err := client.Do(req) @@ -151,6 +171,7 @@ func downloadReleaseData(releaseChecksumURL string, timeout time.Duration) (data if resp == nil { return data, fmt.Errorf("No response from server to download URL %s", releaseChecksumURL) } + defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return data, fmt.Errorf("Error downloading URL %s. Response: %v", releaseChecksumURL, resp.Status) @@ -166,8 +187,8 @@ func downloadReleaseData(releaseChecksumURL string, timeout time.Duration) (data } // DownloadReleaseData - downloads release data from minio official server. -func DownloadReleaseData(timeout time.Duration) (data string, err error) { - return downloadReleaseData(minioReleaseURL+"minio.shasum", timeout) +func DownloadReleaseData(timeout time.Duration, mode string) (data string, err error) { + return downloadReleaseData(minioReleaseURL+"minio.shasum", timeout, mode) } func parseReleaseData(data string) (releaseTime time.Time, err error) { @@ -188,7 +209,7 @@ func parseReleaseData(data string) (releaseTime time.Time, err error) { return releaseTime, err } - releaseTime, err = time.Parse(releaseTagTimeLayout, fields[2]) + releaseTime, err = time.Parse(minioReleaseTagTimeLayout, fields[2]) if err != nil { err = fmt.Errorf("Unknown release time format. %s", err) } @@ -196,8 +217,8 @@ func parseReleaseData(data string) (releaseTime time.Time, err error) { return releaseTime, err } -func getLatestReleaseTime(timeout time.Duration) (releaseTime time.Time, err error) { - data, err := DownloadReleaseData(timeout) +func getLatestReleaseTime(timeout time.Duration, mode string) (releaseTime time.Time, err error) { + data, err := DownloadReleaseData(timeout, mode) if err != nil { return releaseTime, err } @@ -217,13 +238,13 @@ func getDownloadURL() (downloadURL string) { return minioReleaseURL + "minio" } -func getUpdateInfo(timeout time.Duration) (older time.Duration, downloadURL string, err error) { +func getUpdateInfo(timeout time.Duration, mode string) (older time.Duration, downloadURL string, err error) { currentReleaseTime, err := GetCurrentReleaseTime() if err != nil { return older, downloadURL, err } - latestReleaseTime, err := getLatestReleaseTime(timeout) + latestReleaseTime, err := getLatestReleaseTime(timeout, mode) if err != nil { return older, downloadURL, err } @@ -248,7 +269,8 @@ func mainUpdate(ctx *cli.Context) { } } - older, downloadURL, err := getUpdateInfo(10 * time.Second) + minioMode := "" + older, downloadURL, err := getUpdateInfo(10*time.Second, minioMode) if err != nil { quietPrintln(err) os.Exit(-1) diff --git a/cmd/update-main_test.go b/cmd/update-main_test.go index 364e698c5..0a3a746ea 100644 --- a/cmd/update-main_test.go +++ b/cmd/update-main_test.go @@ -17,11 +17,14 @@ package cmd import ( + "errors" "fmt" "io/ioutil" "net/http" "net/http/httptest" "os" + "os/exec" + "path/filepath" "runtime" "testing" "time" @@ -32,29 +35,59 @@ func TestGetCurrentReleaseTime(t *testing.T) { releaseTime1, _ := time.Parse(time.RFC3339, minioVersion1) minioVersion2 := "DEVELOPMENT.GOGET" - tmpfile, err := ioutil.TempFile("", "get-current-release-time-testcase") + tmpfile1, err := ioutil.TempFile("", "get-current-release-time-testcase-1") if err != nil { t.Fatalf("Unable to create temporary file. %s", err) } - defer os.Remove(tmpfile.Name()) - minioBinaryPath2 := tmpfile.Name() - fi, err := tmpfile.Stat() + defer os.Remove(tmpfile1.Name()) + + minioBinaryPath2 := tmpfile1.Name() + fi, err := tmpfile1.Stat() if err != nil { t.Fatalf("Unable to get temporary file info. %s", err) } - if err = tmpfile.Close(); err != nil { + if err = tmpfile1.Close(); err != nil { t.Fatalf("Unable to create temporary file. %s", err) } releaseTime2 := fi.ModTime().UTC() - errorMessage1 := "Unable to get ModTime of . stat : no such file or directory" - if runtime.GOOS == "windows" { - errorMessage1 = "Unable to get ModTime of . Lstat : The system cannot find the path specified." + minioBinaryPath3 := "go" + if runtime.GOOS == globalWindowsOSName { + minioBinaryPath3 = "go.exe" } + goBinAbsPath, err := exec.LookPath(minioBinaryPath3) + if err != nil { + t.Fatal(err) + } + fi, err = os.Stat(goBinAbsPath) + if err != nil { + t.Fatal(err) + } + releaseTime3 := fi.ModTime().UTC() - errorMessage2 := "Unable to get ModTime of non-existing-file. stat non-existing-file: no such file or directory" + // Get a non-absolute binary path. + minioBinaryPath4 := filepath.Base(tmpfile1.Name()) + + // Get a non-existent absolute binary path + minioBinaryPath5 := "/tmp/non-existent-file" + if runtime.GOOS == globalWindowsOSName { + minioBinaryPath5 = "C:\\tmp\\non-existent-file" + } + errorMessage1 := "exec: \"\": executable file not found in $PATH" + if runtime.GOOS == globalWindowsOSName { + errorMessage1 = "exec: \"\": executable file not found in %PATH%" + } + errorMessage2 := "exec: \"non-existent-file\": executable file not found in $PATH" + if runtime.GOOS == globalWindowsOSName { + errorMessage2 = "exec: \"non-existent-file\": executable file not found in %PATH%" + } + errorMessage3 := fmt.Sprintf("exec: \"%s\": executable file not found in $PATH", minioBinaryPath4) + if runtime.GOOS == globalWindowsOSName { + errorMessage3 = "exec: \"" + minioBinaryPath4 + "\": executable file not found in %PATH%" + } + errorMessage4 := "Unable to get ModTime of /tmp/non-existent-file. stat /tmp/non-existent-file: no such file or directory" if runtime.GOOS == "windows" { - errorMessage2 = "Unable to get ModTime of non-existing-file. GetFileAttributesEx non-existing-file: The system cannot find the file specified." + errorMessage4 = "Unable to get ModTime of C:\\tmp\\non-existent-file. GetFileAttributesEx C:\\tmp\\non-existent-file: The system cannot find the path specified." } testCases := []struct { @@ -66,11 +99,14 @@ func TestGetCurrentReleaseTime(t *testing.T) { {minioVersion1, "", releaseTime1, nil}, {minioVersion1, minioBinaryPath2, releaseTime1, nil}, {minioVersion2, minioBinaryPath2, releaseTime2, nil}, + {minioVersion2, minioBinaryPath3, releaseTime3, nil}, {"junk", minioBinaryPath2, releaseTime2, nil}, {"3.2.0", minioBinaryPath2, releaseTime2, nil}, - {minioVersion2, "", time.Time{}, fmt.Errorf(errorMessage1)}, - {"junk", "non-existing-file", time.Time{}, fmt.Errorf(errorMessage2)}, - {"3.2.0", "non-existing-file", time.Time{}, fmt.Errorf(errorMessage2)}, + {minioVersion2, "", time.Time{}, errors.New(errorMessage1)}, + {"junk", "non-existent-file", time.Time{}, errors.New(errorMessage2)}, + {"3.2.0", "non-existent-file", time.Time{}, errors.New(errorMessage2)}, + {minioVersion2, minioBinaryPath4, time.Time{}, errors.New(errorMessage3)}, + {minioVersion2, minioBinaryPath5, time.Time{}, errors.New(errorMessage4)}, } if runtime.GOOS == "linux" { @@ -79,7 +115,7 @@ func TestGetCurrentReleaseTime(t *testing.T) { minioBinaryPath string expectedResult time.Time expectedErr error - }{"3.2a", "/proc/1/cwd", time.Time{}, fmt.Errorf("Unable to get ModTime of /proc/1/cwd. stat /proc/1/cwd: permission denied")}) + }{"3.2a", "/proc/1/cwd", time.Time{}, errors.New("Unable to get ModTime of /proc/1/cwd. stat /proc/1/cwd: permission denied")}) } for _, testCase := range testCases { @@ -224,7 +260,7 @@ func TestDownloadReleaseData(t *testing.T) { } for _, testCase := range testCases { - result, err := downloadReleaseData(testCase.releaseChecksumURL, 1*time.Second) + result, err := downloadReleaseData(testCase.releaseChecksumURL, 1*time.Second, "") if testCase.expectedErr == nil { if err != nil { t.Fatalf("error: expected: %v, got: %v", testCase.expectedErr, err) @@ -242,7 +278,7 @@ func TestDownloadReleaseData(t *testing.T) { } func TestParseReleaseData(t *testing.T) { - releaseTime, _ := time.Parse(releaseTagTimeLayout, "2016-10-07T01-16-39Z") + releaseTime, _ := time.Parse(minioReleaseTagTimeLayout, "2016-10-07T01-16-39Z") testCases := []struct { data string expectedResult time.Time diff --git a/cmd/utils.go b/cmd/utils.go index a6a158d48..744e7bd08 100644 --- a/cmd/utils.go +++ b/cmd/utils.go @@ -19,10 +19,12 @@ package cmd import ( "encoding/base64" "encoding/xml" + "errors" "fmt" "io" "net/http" "net/url" + "os" "strings" "encoding/json" @@ -168,27 +170,41 @@ func checkValidMD5(md5 string) ([]byte, error) { /// http://docs.aws.amazon.com/AmazonS3/latest/dev/UploadingObjects.html const ( - // maximum object size per PUT request is 5GiB - maxObjectSize = 5 * humanize.GiByte - // minimum Part size for multipart upload is 5MiB - minPartSize = 5 * humanize.MiByte - // maximum Part ID for multipart upload is 10000 (Acceptable values range from 1 to 10000 inclusive) - maxPartID = 10000 + // Maximum object size per PUT request is 16GiB. + // This is a divergence from S3 limit on purpose to support + // use cases where users are going to upload large files + // using 'curl' and presigned URL. + globalMaxObjectSize = 16 * humanize.GiByte + + // Minimum Part size for multipart upload is 5MiB + globalMinPartSize = 5 * humanize.MiByte + + // Maximum Part size for multipart upload is 5GiB + globalMaxPartSize = 5 * humanize.GiByte + + // Maximum Part ID for multipart upload is 10000 + // (Acceptable values range from 1 to 10000 inclusive) + globalMaxPartID = 10000 ) // isMaxObjectSize - verify if max object size func isMaxObjectSize(size int64) bool { - return size > maxObjectSize + return size > globalMaxObjectSize +} + +// // Check if part size is more than maximum allowed size. +func isMaxAllowedPartSize(size int64) bool { + return size > globalMaxPartSize } // Check if part size is more than or equal to minimum allowed size. func isMinAllowedPartSize(size int64) bool { - return size >= minPartSize + return size >= globalMinPartSize } // isMaxPartNumber - Check if part ID is greater than the maximum allowed ID. func isMaxPartID(partID int) bool { - return partID > maxPartID + return partID > globalMaxPartID } func contains(stringList []string, element string) bool { @@ -238,3 +254,24 @@ func dumpRequest(r *http.Request) string { } return string(jsonBytes) } + +// isFile - returns whether given path is a file or not. +func isFile(path string) bool { + if fi, err := os.Stat(path); err == nil { + return fi.Mode().IsRegular() + } + + return false +} + +// checkURL - checks if passed address correspond +func checkURL(address string) (*url.URL, error) { + if address == "" { + return nil, errors.New("Address cannot be empty") + } + u, err := url.Parse(address) + if err != nil { + return nil, fmt.Errorf("`%s` invalid: %s", address, err.Error()) + } + return u, nil +} diff --git a/cmd/utils_test.go b/cmd/utils_test.go index d2b2ba9f6..fa06e8e09 100644 --- a/cmd/utils_test.go +++ b/cmd/utils_test.go @@ -110,12 +110,12 @@ func TestMaxObjectSize(t *testing.T) { // Test - 1 - maximum object size. { true, - maxObjectSize + 1, + globalMaxObjectSize + 1, }, // Test - 2 - not maximum object size. { false, - maxObjectSize - 1, + globalMaxObjectSize - 1, }, } for i, s := range sizes { @@ -135,12 +135,12 @@ func TestMinAllowedPartSize(t *testing.T) { // Test - 1 - within minimum part size. { true, - minPartSize + 1, + globalMinPartSize + 1, }, // Test - 2 - smaller than minimum part size. { false, - minPartSize - 1, + globalMinPartSize - 1, }, } @@ -161,12 +161,12 @@ func TestMaxPartID(t *testing.T) { // Test - 1 part number within max part number. { false, - maxPartID - 1, + globalMaxPartID - 1, }, // Test - 2 part number bigger than max part number. { true, - maxPartID + 1, + globalMaxPartID + 1, }, } @@ -388,3 +388,30 @@ func TestLocalAddress(t *testing.T) { } } + +// TestCheckURL tests valid address +func TestCheckURL(t *testing.T) { + testCases := []struct { + addr string + shouldPass bool + }{ + {"", false}, + {":", false}, + {"localhost", true}, + {"127.0.0.1", true}, + {"http://localhost/", true}, + {"http://127.0.0.1/", true}, + {"proto://myhostname/path", true}, + } + + // Validates fetching local address. + for i, testCase := range testCases { + _, err := checkURL(testCase.addr) + if testCase.shouldPass && err != nil { + t.Errorf("Test %d: expected to pass but got an error: %v\n", i+1, err) + } + if !testCase.shouldPass && err == nil { + t.Errorf("Test %d: expected to fail but passed.", i+1) + } + } +} diff --git a/cmd/version-main.go b/cmd/version-main.go index f3d547c4f..9e1b1ea71 100644 --- a/cmd/version-main.go +++ b/cmd/version-main.go @@ -29,10 +29,15 @@ var versionCmd = cli.Command{ {{.HelpName}} - {{.Usage}} USAGE: - {{.HelpName}} - -VERSION: - ` + Version + `{{"\n"}}`, + {{.HelpName}}{{if .VisibleFlags}} [FLAGS]{{end}} +{{if .VisibleFlags}} +FLAGS: + {{range .VisibleFlags}}{{.}} + {{end}}{{end}} +EXAMPLES: + 1. Prints server version: + $ {{.HelpName}} +`, } func mainVersion(ctx *cli.Context) { diff --git a/cmd/web-handlers.go b/cmd/web-handlers.go index c988ab835..4c7bc8754 100644 --- a/cmd/web-handlers.go +++ b/cmd/web-handlers.go @@ -84,7 +84,7 @@ func (web *webAPIHandlers) ServerInfo(r *http.Request, args *WebGenericArgs, rep reply.MinioMemory = mem reply.MinioPlatform = platform reply.MinioRuntime = goruntime - reply.UIVersion = miniobrowser.UIVersion + reply.UIVersion = browser.UIVersion return nil } @@ -104,7 +104,7 @@ func (web *webAPIHandlers) StorageInfo(r *http.Request, args *AuthRPCArgs, reply return toJSONError(errAuthentication) } reply.StorageInfo = objectAPI.StorageInfo() - reply.UIVersion = miniobrowser.UIVersion + reply.UIVersion = browser.UIVersion return nil } @@ -113,7 +113,7 @@ type MakeBucketArgs struct { BucketName string `json:"bucketName"` } -// MakeBucket - make a bucket. +// MakeBucket - creates a new bucket. func (web *webAPIHandlers) MakeBucket(r *http.Request, args *MakeBucketArgs, reply *WebGenericRep) error { objectAPI := web.ObjectAPI() if objectAPI == nil { @@ -122,13 +122,20 @@ func (web *webAPIHandlers) MakeBucket(r *http.Request, args *MakeBucketArgs, rep if !isHTTPRequestValid(r) { return toJSONError(errAuthentication) } + + // Check if bucket is a reserved bucket name. + if isMinioMetaBucket(args.BucketName) || isMinioReservedBucket(args.BucketName) { + return toJSONError(errReservedBucket) + } + bucketLock := globalNSMutex.NewNSLock(args.BucketName, "") bucketLock.Lock() defer bucketLock.Unlock() if err := objectAPI.MakeBucket(args.BucketName); err != nil { return toJSONError(err, args.BucketName) } - reply.UIVersion = miniobrowser.UIVersion + + reply.UIVersion = browser.UIVersion return nil } @@ -161,16 +168,12 @@ func (web *webAPIHandlers) ListBuckets(r *http.Request, args *WebGenericArgs, re return toJSONError(err) } for _, bucket := range buckets { - if bucket.Name == path.Base(reservedBucket) { - continue - } - reply.Buckets = append(reply.Buckets, WebBucketInfo{ Name: bucket.Name, CreationDate: bucket.Created, }) } - reply.UIVersion = miniobrowser.UIVersion + reply.UIVersion = browser.UIVersion return nil } @@ -204,7 +207,7 @@ type WebObjectInfo struct { // ListObjects - list objects api. func (web *webAPIHandlers) ListObjects(r *http.Request, args *ListObjectsArgs, reply *ListObjectsRep) error { - reply.UIVersion = miniobrowser.UIVersion + reply.UIVersion = browser.UIVersion objectAPI := web.ObjectAPI() if objectAPI == nil { return toJSONError(errServerNotInitialized) @@ -253,10 +256,12 @@ func (web *webAPIHandlers) ListObjects(r *http.Request, args *ListObjectsArgs, r } // RemoveObjectArgs - args to remove an object +// JSON will look like: +// '{"bucketname":"testbucket","objects":["photos/hawaii/","photos/maldives/","photos/sanjose.jpg"]}' type RemoveObjectArgs struct { - TargetHost string `json:"targetHost"` - BucketName string `json:"bucketName"` - ObjectName string `json:"objectName"` + Objects []string `json:"objects"` // can be files or sub-directories + Prefix string `json:"prefix"` // current directory in the browser-ui + BucketName string `json:"bucketname"` // bucket name. } // RemoveObject - removes an object. @@ -268,33 +273,65 @@ func (web *webAPIHandlers) RemoveObject(r *http.Request, args *RemoveObjectArgs, if !isHTTPRequestValid(r) { return toJSONError(errAuthentication) } - - objectLock := globalNSMutex.NewNSLock(args.BucketName, args.ObjectName) - objectLock.Lock() - defer objectLock.Unlock() - - if err := objectAPI.DeleteObject(args.BucketName, args.ObjectName); err != nil { - if isErrObjectNotFound(err) { - // Ignore object not found error. - reply.UIVersion = miniobrowser.UIVersion - return nil + if args.BucketName == "" || len(args.Objects) == 0 { + return toJSONError(errUnexpected) + } + var err error +objectLoop: + for _, object := range args.Objects { + remove := func(objectName string) error { + objectLock := globalNSMutex.NewNSLock(args.BucketName, objectName) + objectLock.Lock() + defer objectLock.Unlock() + err = objectAPI.DeleteObject(args.BucketName, objectName) + if err == nil { + // Notify object deleted event. + eventNotify(eventData{ + Type: ObjectRemovedDelete, + Bucket: args.BucketName, + ObjInfo: ObjectInfo{ + Name: objectName, + }, + ReqParams: extractReqParams(r), + }) + } + return err + } + if !hasSuffix(object, slashSeparator) { + // If not a directory, remove the object. + err = remove(object) + if err != nil { + break objectLoop + } + continue + } + // For directories, list the contents recursively and remove. + marker := "" + for { + var lo ListObjectsInfo + lo, err = objectAPI.ListObjects(args.BucketName, object, marker, "", 1000) + if err != nil { + break objectLoop + } + marker = lo.NextMarker + for _, obj := range lo.Objects { + err = remove(obj.Name) + if err != nil { + break objectLoop + } + } + if !lo.IsTruncated { + break + } } - return toJSONError(err, args.BucketName, args.ObjectName) } - // Notify object deleted event. - eventNotify(eventData{ - Type: ObjectRemovedDelete, - Bucket: args.BucketName, - ObjInfo: ObjectInfo{ - Name: args.ObjectName, - }, - ReqParams: map[string]string{ - "sourceIPAddress": r.RemoteAddr, - }, - }) + if err != nil && !isErrObjectNotFound(err) { + // Ignore object not found error. + return toJSONError(err, args.BucketName, "") + } - reply.UIVersion = miniobrowser.UIVersion + reply.UIVersion = browser.UIVersion return nil } @@ -321,7 +358,7 @@ func (web *webAPIHandlers) Login(r *http.Request, args *LoginArgs, reply *LoginR } reply.Token = token - reply.UIVersion = miniobrowser.UIVersion + reply.UIVersion = browser.UIVersion return nil } @@ -336,10 +373,10 @@ func (web webAPIHandlers) GenerateAuth(r *http.Request, args *WebGenericArgs, re if !isHTTPRequestValid(r) { return toJSONError(errAuthentication) } - cred := newCredential() + cred := mustGetNewCredential() reply.AccessKey = cred.AccessKey reply.SecretKey = cred.SecretKey - reply.UIVersion = miniobrowser.UIVersion + reply.UIVersion = browser.UIVersion return nil } @@ -367,16 +404,11 @@ func (web *webAPIHandlers) SetAuth(r *http.Request, args *SetAuthArgs, reply *Se return toJSONError(errChangeCredNotAllowed) } - // As we already validated the authentication, we save given access/secret keys. - if err := validateAuthKeys(args.AccessKey, args.SecretKey); err != nil { + creds, err := createCredential(args.AccessKey, args.SecretKey) + if err != nil { return toJSONError(err) } - creds := credential{ - AccessKey: args.AccessKey, - SecretKey: args.SecretKey, - } - // Notify all other Minio peers to update credentials errsMap := updateCredsOnPeers(creds) @@ -384,7 +416,7 @@ func (web *webAPIHandlers) SetAuth(r *http.Request, args *SetAuthArgs, reply *Se serverConfig.SetCredential(creds) // Persist updated credentials. - if err := serverConfig.Save(); err != nil { + if err = serverConfig.Save(); err != nil { errsMap[globalMinioAddr] = err } @@ -420,7 +452,7 @@ func (web *webAPIHandlers) SetAuth(r *http.Request, args *SetAuthArgs, reply *Se } reply.Token = token - reply.UIVersion = miniobrowser.UIVersion + reply.UIVersion = browser.UIVersion return nil } @@ -439,7 +471,7 @@ func (web *webAPIHandlers) GetAuth(r *http.Request, args *WebGenericArgs, reply creds := serverConfig.GetCredential() reply.AccessKey = creds.AccessKey reply.SecretKey = creds.SecretKey - reply.UIVersion = miniobrowser.UIVersion + reply.UIVersion = browser.UIVersion return nil } @@ -489,12 +521,10 @@ func (web *webAPIHandlers) Upload(w http.ResponseWriter, r *http.Request) { // Notify object created event. eventNotify(eventData{ - Type: ObjectCreatedPut, - Bucket: bucket, - ObjInfo: objInfo, - ReqParams: map[string]string{ - "sourceIPAddress": r.RemoteAddr, - }, + Type: ObjectCreatedPut, + Bucket: bucket, + ObjInfo: objInfo, + ReqParams: extractReqParams(r), }) } @@ -584,7 +614,7 @@ func (web *webAPIHandlers) DownloadZip(w http.ResponseWriter, r *http.Request) { return objectAPI.GetObject(args.BucketName, objectName, 0, info.Size, writer) } - if !strings.HasSuffix(object, "/") { + if !hasSuffix(object, slashSeparator) { // If not a directory, compress the file and write it to response. err := zipit(pathJoin(args.Prefix, object)) if err != nil { @@ -667,7 +697,7 @@ func (web *webAPIHandlers) GetBucketPolicy(r *http.Request, args *GetBucketPolic return toJSONError(err, args.BucketName) } - reply.UIVersion = miniobrowser.UIVersion + reply.UIVersion = browser.UIVersion reply.Policy = policy.GetPolicy(policyInfo.Statements, args.BucketName, args.Prefix) return nil @@ -678,8 +708,8 @@ type ListAllBucketPoliciesArgs struct { BucketName string `json:"bucketName"` } -// Collection of canned bucket policy at a given prefix. -type bucketAccessPolicy struct { +// BucketAccessPolicy - Collection of canned bucket policy at a given prefix. +type BucketAccessPolicy struct { Prefix string `json:"prefix"` Policy policy.BucketPolicy `json:"policy"` } @@ -687,7 +717,7 @@ type bucketAccessPolicy struct { // ListAllBucketPoliciesRep - get all bucket policy reply. type ListAllBucketPoliciesRep struct { UIVersion string `json:"uiVersion"` - Policies []bucketAccessPolicy `json:"policies"` + Policies []BucketAccessPolicy `json:"policies"` } // GetllBucketPolicy - get all bucket policy. @@ -706,9 +736,9 @@ func (web *webAPIHandlers) ListAllBucketPolicies(r *http.Request, args *ListAllB return toJSONError(err, args.BucketName) } - reply.UIVersion = miniobrowser.UIVersion + reply.UIVersion = browser.UIVersion for prefix, policy := range policy.GetPolicies(policyInfo.Statements, args.BucketName) { - reply.Policies = append(reply.Policies, bucketAccessPolicy{ + reply.Policies = append(reply.Policies, BucketAccessPolicy{ Prefix: prefix, Policy: policy, }) @@ -751,7 +781,7 @@ func (web *webAPIHandlers) SetBucketPolicy(r *http.Request, args *SetBucketPolic if err != nil { return toJSONError(err, args.BucketName) } - reply.UIVersion = miniobrowser.UIVersion + reply.UIVersion = browser.UIVersion return nil } data, err := json.Marshal(policyInfo) @@ -770,7 +800,7 @@ func (web *webAPIHandlers) SetBucketPolicy(r *http.Request, args *SetBucketPolic } return toJSONError(err, args.BucketName) } - reply.UIVersion = miniobrowser.UIVersion + reply.UIVersion = browser.UIVersion return nil } @@ -807,7 +837,7 @@ func (web *webAPIHandlers) PresignedGet(r *http.Request, args *PresignedGetArgs, Message: "Bucket and Object are mandatory arguments.", } } - reply.UIVersion = miniobrowser.UIVersion + reply.UIVersion = browser.UIVersion reply.URL = presignedGet(args.HostName, args.BucketName, args.ObjectName, args.Expiry) return nil } @@ -858,6 +888,13 @@ func toJSONError(err error, params ...string) (jerr *json2.Error) { Message: apiErr.Description, } switch apiErr.Code { + // Reserved bucket name provided. + case "AllAccessDisabled": + if len(params) > 0 { + jerr = &json2.Error{ + Message: fmt.Sprintf("All access to this bucket %s has been disabled.", params[0]), + } + } // Bucket name invalid with custom error message. case "InvalidBucketName": if len(params) > 0 { @@ -929,6 +966,12 @@ func toWebAPIError(err error) APIError { HTTPStatusCode: http.StatusMethodNotAllowed, Description: err.Error(), } + } else if err == errReservedBucket { + return APIError{ + Code: "AllAccessDisabled", + HTTPStatusCode: http.StatusForbidden, + Description: err.Error(), + } } // Convert error type to api error code. var apiErrCode APIErrorCode diff --git a/cmd/web-handlers_test.go b/cmd/web-handlers_test.go index 66f7cbd52..ffe9c9bed 100644 --- a/cmd/web-handlers_test.go +++ b/cmd/web-handlers_test.go @@ -265,6 +265,8 @@ func testMakeBucketWebHandler(obj ObjectLayer, instanceType string, t TestErrHan {"", false}, {".", false}, {"ab", false}, + {"minio", false}, + {".minio.sys", false}, {bucketName, true}, } @@ -468,7 +470,14 @@ func testRemoveObjectWebHandler(obj ObjectLayer, instanceType string, t TestErrH t.Fatalf("Was not able to upload an object, %v", err) } - removeObjectRequest := RemoveObjectArgs{BucketName: bucketName, ObjectName: objectName} + objectName = "a/object" + _, err = obj.PutObject(bucketName, objectName, int64(len(data)), bytes.NewReader(data), + map[string]string{"md5Sum": "c9a34cfc85d982698c6ac89f76071abd"}, "") + if err != nil { + t.Fatalf("Was not able to upload an object, %v", err) + } + + removeObjectRequest := RemoveObjectArgs{BucketName: bucketName, Objects: []string{"a/", "object"}} removeObjectReply := &WebGenericRep{} req, err := newTestWebRPCRequest("Web.RemoveObject", authorization, removeObjectRequest) if err != nil { @@ -483,7 +492,7 @@ func testRemoveObjectWebHandler(obj ObjectLayer, instanceType string, t TestErrH t.Fatalf("Failed, %v", err) } - removeObjectRequest = RemoveObjectArgs{BucketName: bucketName, ObjectName: objectName} + removeObjectRequest = RemoveObjectArgs{BucketName: bucketName, Objects: []string{"a/", "object"}} removeObjectReply = &WebGenericRep{} req, err = newTestWebRPCRequest("Web.RemoveObject", authorization, removeObjectRequest) if err != nil { @@ -687,7 +696,7 @@ func testUploadWebHandler(obj ObjectLayer, instanceType string, t TestErrHandler t.Fatalf("Failed, %v", err) } - if bytes.Compare(byteBuffer.Bytes(), content) != 0 { + if !bytes.Equal(byteBuffer.Bytes(), content) { t.Fatalf("The upload file is different from the download file") } @@ -773,7 +782,7 @@ func testDownloadWebHandler(obj ObjectLayer, instanceType string, t TestErrHandl t.Fatalf("Expected the response status to be 200, but instead found `%d`", code) } - if bytes.Compare(bodyContent, content) != 0 { + if !bytes.Equal(bodyContent, content) { t.Fatalf("The downloaded file is corrupted") } @@ -796,7 +805,7 @@ func testDownloadWebHandler(obj ObjectLayer, instanceType string, t TestErrHandl t.Fatalf("Expected the response status to be 200, but instead found `%d`", code) } - if bytes.Compare(bodyContent, content) != 0 { + if !bytes.Equal(bodyContent, content) { t.Fatalf("The downloaded file is corrupted") } } @@ -849,7 +858,7 @@ func testWebHandlerDownloadZip(obj ObjectLayer, instanceType string, t TestErrHa return 0, nil } var req *http.Request - req, err = http.NewRequest("GET", path, bytes.NewBuffer(argsData)) + req, err = http.NewRequest("POST", path, bytes.NewBuffer(argsData)) if err != nil { t.Fatalf("Cannot create upload request, %v", err) @@ -1127,13 +1136,13 @@ func testWebListAllBucketPoliciesHandler(obj ObjectLayer, instanceType string, t t.Fatal("Unexpected error: ", err) } - testCaseResult1 := []bucketAccessPolicy{{ + testCaseResult1 := []BucketAccessPolicy{{ Prefix: bucketName + "/hello*", Policy: policy.BucketPolicyReadWrite, }} testCases := []struct { bucketName string - expectedResult []bucketAccessPolicy + expectedResult []BucketAccessPolicy }{ {bucketName, testCaseResult1}, } @@ -1413,6 +1422,12 @@ func TestWebObjectLayerFaultyDisks(t *testing.T) { // Executing the object layer tests for XL. defer removeRoots(fsDirs) + bucketName := "mybucket" + err = obj.MakeBucket(bucketName) + if err != nil { + t.Fatal("Cannot make bucket:", err) + } + // Set faulty disks to XL backend xl := obj.(*xlObjects) for i, d := range xl.storageDisks { @@ -1440,13 +1455,23 @@ func TestWebObjectLayerFaultyDisks(t *testing.T) { } // Check if web rpc calls return errors with faulty disks. ServerInfo, GenerateAuth, SetAuth, GetAuth are not concerned - webRPCs := []string{"MakeBucket", "ListBuckets", "ListObjects", "RemoveObject", - "GetBucketPolicy", "SetBucketPolicy"} + // RemoveObject is also not concerned since it always returns success. + webRPCs := []struct { + webRPCName string + ReqArgs interface{} + RepArgs interface{} + }{ + {"MakeBucket", MakeBucketArgs{BucketName: bucketName}, WebGenericRep{}}, + {"ListBuckets", AuthRPCArgs{}, ListBucketsRep{}}, + {"ListObjects", ListObjectsArgs{BucketName: bucketName, Prefix: ""}, ListObjectsRep{}}, + {"GetBucketPolicy", GetBucketPolicyArgs{BucketName: bucketName, Prefix: ""}, GetBucketPolicyRep{}}, + {"SetBucketPolicy", SetBucketPolicyArgs{BucketName: bucketName, Prefix: "", Policy: "none"}, WebGenericRep{}}, + } for _, rpcCall := range webRPCs { - args := &AuthRPCArgs{} - reply := &WebGenericRep{} - req, nerr := newTestWebRPCRequest("Web."+rpcCall, authorization, args) + args := &rpcCall.ReqArgs + reply := &rpcCall.RepArgs + req, nerr := newTestWebRPCRequest("Web."+rpcCall.webRPCName, authorization, args) if nerr != nil { t.Fatalf("Test %s: Failed to create HTTP request: %v", rpcCall, nerr) } diff --git a/cmd/web-router.go b/cmd/web-router.go index c4fdfbb2c..6f85156c3 100644 --- a/cmd/web-router.go +++ b/cmd/web-router.go @@ -39,7 +39,7 @@ type indexHandler struct { } func (h indexHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - r.URL.Path = reservedBucket + "/" + r.URL.Path = minioReservedBucketPath + "/" h.handler.ServeHTTP(w, r) } @@ -47,9 +47,9 @@ const assetPrefix = "production" func assetFS() *assetfs.AssetFS { return &assetfs.AssetFS{ - Asset: miniobrowser.Asset, - AssetDir: miniobrowser.AssetDir, - AssetInfo: miniobrowser.AssetInfo, + Asset: browser.Asset, + AssetDir: browser.AssetDir, + AssetInfo: browser.AssetInfo, Prefix: assetPrefix, } } @@ -68,7 +68,7 @@ func registerWebRouter(mux *router.Router) error { codec := json2.NewCodec() // Minio browser router. - webBrowserRouter := mux.NewRoute().PathPrefix(reservedBucket).Subrouter() + webBrowserRouter := mux.NewRoute().PathPrefix(minioReservedBucketPath).Subrouter() // Initialize json rpc handlers. webRPC := jsonrpc.NewServer() @@ -84,16 +84,16 @@ func registerWebRouter(mux *router.Router) error { webBrowserRouter.Methods("POST").Path("/webrpc").Handler(webRPC) webBrowserRouter.Methods("PUT").Path("/upload/{bucket}/{object:.+}").HandlerFunc(web.Upload) webBrowserRouter.Methods("GET").Path("/download/{bucket}/{object:.+}").Queries("token", "{token:.*}").HandlerFunc(web.Download) - webBrowserRouter.Methods("GET").Path("/zip").Queries("token", "{token:.*}").HandlerFunc(web.DownloadZip) + webBrowserRouter.Methods("POST").Path("/zip").Queries("token", "{token:.*}").HandlerFunc(web.DownloadZip) // Add compression for assets. - compressedAssets := handlers.CompressHandler(http.StripPrefix(reservedBucket, http.FileServer(assetFS()))) + compressedAssets := handlers.CompressHandler(http.StripPrefix(minioReservedBucketPath, http.FileServer(assetFS()))) // Serve javascript files and favicon from assets. webBrowserRouter.Path(fmt.Sprintf("/{assets:[^/]+.js|%s}", specialAssets)).Handler(compressedAssets) // Serve index.html for rest of the requests. - webBrowserRouter.Path("/{index:.*}").Handler(indexHandler{http.StripPrefix(reservedBucket, http.FileServer(assetFS()))}) + webBrowserRouter.Path("/{index:.*}").Handler(indexHandler{http.StripPrefix(minioReservedBucketPath, http.FileServer(assetFS()))}) return nil } diff --git a/cmd/xl-v1-bucket.go b/cmd/xl-v1-bucket.go index 4078b7292..f2b9a5af7 100644 --- a/cmd/xl-v1-bucket.go +++ b/cmd/xl-v1-bucket.go @@ -165,13 +165,7 @@ func (xl xlObjects) listBuckets() (bucketsInfo []BucketInfo, err error) { // should take care of this. var bucketsInfo []BucketInfo for _, volInfo := range volsInfo { - // StorageAPI can send volume names which are incompatible - // with buckets, handle it and skip them. - if !IsValidBucketName(volInfo.Name) { - continue - } - // Ignore the volume special bucket. - if volInfo.Name == minioMetaBucket { + if isReservedOrInvalidBucket(volInfo.Name) { continue } bucketsInfo = append(bucketsInfo, BucketInfo{ diff --git a/cmd/xl-v1-healing-common.go b/cmd/xl-v1-healing-common.go index d652bc462..38f79ba6a 100644 --- a/cmd/xl-v1-healing-common.go +++ b/cmd/xl-v1-healing-common.go @@ -16,7 +16,11 @@ package cmd -import "time" +import ( + "encoding/hex" + "path/filepath" + "time" +) // commonTime returns a maximally occurring time from a list of time. func commonTime(modTimes []time.Time) (modTime time.Time, count int) { @@ -59,29 +63,43 @@ func bootModtimes(diskCount int) []time.Time { } // Extracts list of times from xlMetaV1 slice and returns, skips -// slice elements which have errors. As a special error -// errFileNotFound is treated as a initial good condition. +// slice elements which have errors. func listObjectModtimes(partsMetadata []xlMetaV1, errs []error) (modTimes []time.Time) { modTimes = bootModtimes(len(partsMetadata)) - // Set a new time value, specifically set when - // error == errFileNotFound (this is needed when this is a - // fresh PutObject). - timeNow := time.Now().UTC() for index, metadata := range partsMetadata { - if errs[index] == nil { - // Once the file is found, save the uuid saved on disk. - modTimes[index] = metadata.Stat.ModTime - } else if errs[index] == errFileNotFound { - // Once the file is not found then the epoch is current time. - modTimes[index] = timeNow + if errs[index] != nil { + continue } + // Once the file is found, save the uuid saved on disk. + modTimes[index] = metadata.Stat.ModTime } return modTimes } -// Returns slice of online disks needed. -// - slice returing readable disks. -// - modTime of the Object +// Notes: +// There are 5 possible states a disk could be in, +// 1. __online__ - has the latest copy of xl.json - returned by listOnlineDisks +// +// 2. __offline__ - err == errDiskNotFound +// +// 3. __availableWithParts__ - has the latest copy of xl.json and has all +// parts with checksums matching; returned by disksWithAllParts +// +// 4. __outdated__ - returned by outDatedDisk, provided []StorageAPI +// returned by diskWithAllParts is passed for latestDisks. +// - has an old copy of xl.json +// - doesn't have xl.json (errFileNotFound) +// - has the latest xl.json but one or more parts are corrupt +// +// 5. __missingParts__ - has the latest copy of xl.json but has some parts +// missing. This is identified separately since this may need manual +// inspection to understand the root cause. E.g, this could be due to +// backend filesystem corruption. + +// listOnlineDisks - returns +// - a slice of disks where disk having 'older' xl.json (or nothing) +// are set to nil. +// - latest (in time) of the maximally occurring modTime(s). func listOnlineDisks(disks []StorageAPI, partsMetadata []xlMetaV1, errs []error) (onlineDisks []StorageAPI, modTime time.Time) { onlineDisks = make([]StorageAPI, len(disks)) @@ -102,22 +120,23 @@ func listOnlineDisks(disks []StorageAPI, partsMetadata []xlMetaV1, errs []error) return onlineDisks, modTime } -// Return disks with the outdated or missing object. -func outDatedDisks(disks []StorageAPI, partsMetadata []xlMetaV1, errs []error) (outDatedDisks []StorageAPI) { +// outDatedDisks - return disks which don't have the latest object (i.e xl.json). +// disks that are offline are not 'marked' outdated. +func outDatedDisks(disks, latestDisks []StorageAPI, errs []error, partsMetadata []xlMetaV1, + bucket, object string) (outDatedDisks []StorageAPI) { + outDatedDisks = make([]StorageAPI, len(disks)) - latestDisks, _ := listOnlineDisks(disks, partsMetadata, errs) - for index, disk := range latestDisks { - if errorCause(errs[index]) == errFileNotFound { - outDatedDisks[index] = disks[index] + for index, latestDisk := range latestDisks { + if latestDisk != nil { continue } - if errs[index] != nil { - continue - } - if disk == nil { + // disk either has an older xl.json or doesn't have one. + switch errorCause(errs[index]) { + case nil, errFileNotFound: outDatedDisks[index] = disks[index] } } + return outDatedDisks } @@ -189,3 +208,49 @@ func xlHealStat(xl xlObjects, partsMetadata []xlMetaV1, errs []error) HealObject MissingPartityCount: missingParityCount, } } + +// disksWithAllParts - This function needs to be called with +// []StorageAPI returned by listOnlineDisks. Returns, +// - disks which have all parts specified in the latest xl.json. +// - errs updated to have errFileNotFound in place of disks that had +// missing parts. +// - non-nil error if any of the online disks failed during +// calculating blake2b checksum. +func disksWithAllParts(onlineDisks []StorageAPI, partsMetadata []xlMetaV1, errs []error, bucket, object string) ([]StorageAPI, []error, error) { + availableDisks := make([]StorageAPI, len(onlineDisks)) + for index, onlineDisk := range onlineDisks { + if onlineDisk == nil { + continue + } + // disk has a valid xl.json but may not have all the + // parts. This is considered an outdated disk, since + // it needs healing too. + for pIndex, part := range partsMetadata[index].Parts { + // compute blake2b sum of part. + partPath := filepath.Join(object, part.Name) + hash := newHash(blake2bAlgo) + blakeBytes, hErr := hashSum(onlineDisk, bucket, partPath, hash) + if hErr == errFileNotFound { + errs[index] = errFileNotFound + continue + } + + if hErr != nil && hErr != errFileNotFound { + return nil, nil, traceError(hErr) + } + + partChecksum := partsMetadata[index].Erasure.Checksum[pIndex].Hash + blakeSum := hex.EncodeToString(blakeBytes) + // if blake2b sum doesn't match for a part + // then this disk is outdated and needs + // healing. + if blakeSum != partChecksum { + errs[index] = errFileNotFound + break + } + availableDisks[index] = onlineDisk + } + } + + return availableDisks, errs, nil +} diff --git a/cmd/xl-v1-healing-common_test.go b/cmd/xl-v1-healing-common_test.go index be7f200b9..7a3c63db7 100644 --- a/cmd/xl-v1-healing-common_test.go +++ b/cmd/xl-v1-healing-common_test.go @@ -17,6 +17,8 @@ package cmd import ( + "bytes" + "path/filepath" "testing" "time" ) @@ -81,3 +83,250 @@ func TestCommonTime(t *testing.T) { } } } + +// partsMetaFromModTimes - returns slice of modTimes given metadata of +// an object part. +func partsMetaFromModTimes(modTimes []time.Time, checksums []checkSumInfo) []xlMetaV1 { + var partsMetadata []xlMetaV1 + for _, modTime := range modTimes { + partsMetadata = append(partsMetadata, xlMetaV1{ + Erasure: erasureInfo{ + Checksum: checksums, + }, + Stat: statInfo{ + ModTime: modTime, + }, + Parts: []objectPartInfo{ + { + Name: "part.1", + }, + }, + }) + } + return partsMetadata +} + +// toPosix - fetches *posix object from StorageAPI. +func toPosix(disk StorageAPI) *posix { + retryDisk, ok := disk.(*retryStorage) + if !ok { + return nil + } + pDisk, ok := retryDisk.remoteStorage.(*posix) + if !ok { + return nil + } + return pDisk + +} + +// TestListOnlineDisks - checks if listOnlineDisks and outDatedDisks +// are consistent with each other. +func TestListOnlineDisks(t *testing.T) { + rootPath, err := newTestConfig(globalMinioDefaultRegion) + if err != nil { + t.Fatalf("Failed to initialize config - %v", err) + } + defer removeAll(rootPath) + + obj, disks, err := prepareXL() + if err != nil { + t.Fatalf("Prepare XL backend failed - %v", err) + } + defer removeRoots(disks) + + type tamperKind int + const ( + noTamper tamperKind = iota + deletePart tamperKind = iota + corruptPart tamperKind = iota + ) + threeNanoSecs := time.Unix(0, 3).UTC() + fourNanoSecs := time.Unix(0, 4).UTC() + modTimesThreeNone := []time.Time{ + threeNanoSecs, threeNanoSecs, threeNanoSecs, threeNanoSecs, + threeNanoSecs, threeNanoSecs, threeNanoSecs, + timeSentinel, timeSentinel, timeSentinel, timeSentinel, + timeSentinel, timeSentinel, timeSentinel, timeSentinel, + timeSentinel, + } + modTimesThreeFour := []time.Time{ + threeNanoSecs, threeNanoSecs, threeNanoSecs, threeNanoSecs, + threeNanoSecs, threeNanoSecs, threeNanoSecs, threeNanoSecs, + fourNanoSecs, fourNanoSecs, fourNanoSecs, fourNanoSecs, + fourNanoSecs, fourNanoSecs, fourNanoSecs, fourNanoSecs, + } + testCases := []struct { + modTimes []time.Time + expectedTime time.Time + errs []error + _tamperBackend tamperKind + }{ + { + modTimes: modTimesThreeFour, + expectedTime: fourNanoSecs, + errs: []error{ + nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, + }, + _tamperBackend: noTamper, + }, + { + modTimes: modTimesThreeNone, + expectedTime: threeNanoSecs, + errs: []error{ + // Disks that have a valid xl.json. + nil, nil, nil, nil, nil, nil, nil, + // Majority of disks don't have xl.json. + errFileNotFound, errFileNotFound, + errFileNotFound, errFileNotFound, + errFileNotFound, errDiskAccessDenied, + errDiskNotFound, errFileNotFound, + errFileNotFound, + }, + _tamperBackend: deletePart, + }, + { + modTimes: modTimesThreeNone, + expectedTime: threeNanoSecs, + errs: []error{ + // Disks that have a valid xl.json. + nil, nil, nil, nil, nil, nil, nil, + // Majority of disks don't have xl.json. + errFileNotFound, errFileNotFound, + errFileNotFound, errFileNotFound, + errFileNotFound, errDiskAccessDenied, + errDiskNotFound, errFileNotFound, + errFileNotFound, + }, + _tamperBackend: corruptPart, + }, + } + + bucket := "bucket" + object := "object" + data := bytes.Repeat([]byte("a"), 1024) + xlDisks := obj.(*xlObjects).storageDisks + for i, test := range testCases { + // Prepare bucket/object backend for the tests below. + + // Cleanup from previous test. + obj.DeleteObject(bucket, object) + obj.DeleteBucket(bucket) + + err = obj.MakeBucket("bucket") + if err != nil { + t.Fatalf("Failed to make a bucket %v", err) + } + + _, err = obj.PutObject(bucket, object, int64(len(data)), bytes.NewReader(data), nil, "") + if err != nil { + t.Fatalf("Failed to putObject %v", err) + } + + // Fetch xl.json from first disk to construct partsMetadata for the tests. + xlMeta, err := readXLMeta(xlDisks[0], bucket, object) + if err != nil { + t.Fatalf("Test %d: Failed to read xl.json %v", i+1, err) + } + + tamperedIndex := -1 + switch test._tamperBackend { + case deletePart: + for index, err := range test.errs { + if err != nil { + continue + } + // Remove a part from a disk + // which has a valid xl.json, + // and check if that disk + // appears in outDatedDisks. + tamperedIndex = index + dErr := xlDisks[index].DeleteFile(bucket, filepath.Join(object, "part.1")) + if dErr != nil { + t.Fatalf("Test %d: Failed to delete %s - %v", i+1, + filepath.Join(object, "part.1"), dErr) + } + break + } + case corruptPart: + for index, err := range test.errs { + if err != nil { + continue + } + // Corrupt a part from a disk + // which has a valid xl.json, + // and check if that disk + // appears in outDatedDisks. + tamperedIndex = index + dErr := xlDisks[index].AppendFile(bucket, filepath.Join(object, "part.1"), []byte("corruption")) + if dErr != nil { + t.Fatalf("Test %d: Failed to append corrupting data at the end of file %s - %v", + i+1, filepath.Join(object, "part.1"), dErr) + } + break + } + + } + + partsMetadata := partsMetaFromModTimes(test.modTimes, xlMeta.Erasure.Checksum) + + onlineDisks, modTime := listOnlineDisks(xlDisks, partsMetadata, test.errs) + availableDisks, newErrs, err := disksWithAllParts(onlineDisks, partsMetadata, test.errs, bucket, object) + test.errs = newErrs + outdatedDisks := outDatedDisks(xlDisks, availableDisks, test.errs, partsMetadata, bucket, object) + if modTime.Equal(timeSentinel) { + t.Fatalf("Test %d: modTime should never be equal to timeSentinel, but found equal", + i+1) + } + + if test._tamperBackend != noTamper { + if tamperedIndex != -1 && outdatedDisks[tamperedIndex] == nil { + t.Fatalf("Test %d: disk (%v) with part.1 missing is an outdated disk, but wasn't listed by outDatedDisks", + i+1, xlDisks[tamperedIndex]) + } + + } + + if !modTime.Equal(test.expectedTime) { + t.Fatalf("Test %d: Expected modTime to be equal to %v but was found to be %v", + i+1, test.expectedTime, modTime) + } + + // Check if a disk is considered both online and outdated, + // which is a contradiction, except if parts are missing. + overlappingDisks := make(map[string]*posix) + for _, availableDisk := range availableDisks { + if availableDisk == nil { + continue + } + pDisk := toPosix(availableDisk) + overlappingDisks[pDisk.diskPath] = pDisk + } + + for index, outdatedDisk := range outdatedDisks { + // ignore the intentionally tampered disk, + // this is expected to appear as outdated + // disk, since it doesn't have all the parts. + if index == tamperedIndex { + continue + } + + if outdatedDisk == nil { + continue + } + + pDisk := toPosix(outdatedDisk) + if _, ok := overlappingDisks[pDisk.diskPath]; ok { + t.Errorf("Test %d: Outdated disk %v was also detected as an online disk - %v %v", + i+1, pDisk, availableDisks, outdatedDisks) + } + + // errors other than errFileNotFound doesn't imply that the disk is outdated. + if test.errs[index] != nil && test.errs[index] != errFileNotFound && outdatedDisk != nil { + t.Errorf("Test %d: error (%v) other than errFileNotFound doesn't imply that the disk (%v) could be outdated", + i+1, test.errs[index], pDisk) + } + } + } +} diff --git a/cmd/xl-v1-healing.go b/cmd/xl-v1-healing.go index d0aaf56a7..e8d51a7e4 100644 --- a/cmd/xl-v1-healing.go +++ b/cmd/xl-v1-healing.go @@ -252,6 +252,7 @@ func (xl xlObjects) ListBucketsHeal() ([]BucketInfo, error) { if err != nil { return listBuckets, err } + // Iterate over all buckets for _, currBucket := range buckets { // Check the status of bucket metadata @@ -275,6 +276,7 @@ func (xl xlObjects) ListBucketsHeal() ([]BucketInfo, error) { } } + // Sort found buckets sort.Sort(byBucketName(listBuckets)) return listBuckets, nil @@ -284,33 +286,38 @@ func (xl xlObjects) ListBucketsHeal() ([]BucketInfo, error) { // during startup i.e healing of buckets, bucket metadata (policy.json, // notification.xml, listeners.json) etc. Currently this function // supports quick healing of buckets, bucket metadata. -// -// TODO :- -// - add support for healing dangling `uploads.json`. -// - add support for healing dangling `xl.json`. func quickHeal(storageDisks []StorageAPI, writeQuorum int, readQuorum int) error { - // List all bucket names from all disks. - bucketNames, _, err := listAllBuckets(storageDisks) + // List all bucket name occurrence from all disks. + _, bucketOcc, err := listAllBuckets(storageDisks) if err != nil { return err } - // All bucket names and bucket metadata should be healed. - for bucketName := range bucketNames { - // Heal bucket and then proceed to heal bucket metadata. - if err = healBucket(storageDisks, bucketName, writeQuorum); err == nil { - if err = healBucketMetadata(storageDisks, bucketName, readQuorum); err == nil { - continue + + // All bucket names and bucket metadata that should be healed. + for bucketName, occCount := range bucketOcc { + // Heal bucket only if healing is needed. + if occCount != len(storageDisks) { + // Heal bucket and then proceed to heal bucket metadata if any. + if err = healBucket(storageDisks, bucketName, writeQuorum); err == nil { + if err = healBucketMetadata(storageDisks, bucketName, readQuorum); err == nil { + continue + } + return err } return err } - return err } + + // Success. return nil } // Heals an object only the corrupted/missing erasure blocks. func healObject(storageDisks []StorageAPI, bucket string, object string, quorum int) error { partsMetadata, errs := readAllXLMetadata(storageDisks, bucket, object) + // readQuorum suffices for xl.json since we use monotonic + // system time to break the tie when a split-brain situation + // arises. if reducedErr := reduceReadQuorumErrs(errs, nil, quorum); reducedErr != nil { return toObjectErr(reducedErr, bucket, object) } @@ -322,12 +329,35 @@ func healObject(storageDisks []StorageAPI, bucket string, object string, quorum // List of disks having latest version of the object. latestDisks, modTime := listOnlineDisks(storageDisks, partsMetadata, errs) + + // List of disks having all parts as per latest xl.json. + availableDisks, errs, aErr := disksWithAllParts(latestDisks, partsMetadata, errs, bucket, object) + if aErr != nil { + return toObjectErr(aErr, bucket, object) + } + + numAvailableDisks := 0 + for _, disk := range availableDisks { + if disk != nil { + numAvailableDisks++ + } + } + + // If less than read quorum number of disks have all the parts + // of the data, we can't reconstruct the erasure-coded data. + if numAvailableDisks < quorum { + return toObjectErr(errXLReadQuorum, bucket, object) + } + // List of disks having outdated version of the object or missing object. - outDatedDisks := outDatedDisks(storageDisks, partsMetadata, errs) - // Latest xlMetaV1 for reference. If a valid metadata is not present, it is as good as object not found. + outDatedDisks := outDatedDisks(storageDisks, availableDisks, errs, partsMetadata, + bucket, object) + + // Latest xlMetaV1 for reference. If a valid metadata is not + // present, it is as good as object not found. latestMeta, pErr := pickValidXLMeta(partsMetadata, modTime) if pErr != nil { - return pErr + return toObjectErr(pErr, bucket, object) } for index, disk := range outDatedDisks { @@ -357,23 +387,23 @@ func healObject(storageDisks []StorageAPI, bucket string, object string, quorum // Delete all the parts. Ignore if parts are not found. for _, part := range outDatedMeta.Parts { - err := disk.DeleteFile(bucket, pathJoin(object, part.Name)) - if err != nil && !isErr(err, errFileNotFound) { - return traceError(err) + dErr := disk.DeleteFile(bucket, pathJoin(object, part.Name)) + if dErr != nil && !isErr(dErr, errFileNotFound) { + return toObjectErr(traceError(dErr), bucket, object) } } // Delete xl.json file. Ignore if xl.json not found. - err := disk.DeleteFile(bucket, pathJoin(object, xlMetaJSONFile)) - if err != nil && !isErr(err, errFileNotFound) { - return traceError(err) + dErr := disk.DeleteFile(bucket, pathJoin(object, xlMetaJSONFile)) + if dErr != nil && !isErr(dErr, errFileNotFound) { + return toObjectErr(traceError(dErr), bucket, object) } } // Reorder so that we have data disks first and parity disks next. - latestDisks = getOrderedDisks(latestMeta.Erasure.Distribution, latestDisks) - outDatedDisks = getOrderedDisks(latestMeta.Erasure.Distribution, outDatedDisks) - partsMetadata = getOrderedPartsMetadata(latestMeta.Erasure.Distribution, partsMetadata) + latestDisks = shuffleDisks(latestDisks, latestMeta.Erasure.Distribution) + outDatedDisks = shuffleDisks(outDatedDisks, latestMeta.Erasure.Distribution) + partsMetadata = shufflePartsMetadata(partsMetadata, latestMeta.Erasure.Distribution) // We write at temporary location and then rename to fianal location. tmpID := mustGetUUID() @@ -390,12 +420,12 @@ func healObject(storageDisks []StorageAPI, bucket string, object string, quorum erasure := latestMeta.Erasure sumInfo := latestMeta.Erasure.GetCheckSumInfo(partName) // Heal the part file. - checkSums, err := erasureHealFile(latestDisks, outDatedDisks, + checkSums, hErr := erasureHealFile(latestDisks, outDatedDisks, bucket, pathJoin(object, partName), minioMetaTmpBucket, pathJoin(tmpID, partName), partSize, erasure.BlockSize, erasure.DataBlocks, erasure.ParityBlocks, sumInfo.Algorithm) - if err != nil { - return err + if hErr != nil { + return toObjectErr(hErr, bucket, object) } for index, sum := range checkSums { if outDatedDisks[index] != nil { @@ -418,9 +448,9 @@ func healObject(storageDisks []StorageAPI, bucket string, object string, quorum } // Generate and write `xl.json` generated from other disks. - err := writeUniqueXLMetadata(outDatedDisks, minioMetaTmpBucket, tmpID, partsMetadata, diskCount(outDatedDisks)) - if err != nil { - return toObjectErr(err, bucket, object) + aErr = writeUniqueXLMetadata(outDatedDisks, minioMetaTmpBucket, tmpID, partsMetadata, diskCount(outDatedDisks)) + if aErr != nil { + return toObjectErr(aErr, bucket, object) } // Rename from tmp location to the actual location. @@ -429,14 +459,14 @@ func healObject(storageDisks []StorageAPI, bucket string, object string, quorum continue } // Remove any lingering partial data from current namespace. - err = disk.DeleteFile(bucket, retainSlash(object)) - if err != nil && err != errFileNotFound { - return traceError(err) + aErr = disk.DeleteFile(bucket, retainSlash(object)) + if aErr != nil && aErr != errFileNotFound { + return toObjectErr(traceError(aErr), bucket, object) } // Attempt a rename now from healed data to final location. - err = disk.RenameFile(minioMetaTmpBucket, retainSlash(tmpID), bucket, retainSlash(object)) - if err != nil { - return traceError(err) + aErr = disk.RenameFile(minioMetaTmpBucket, retainSlash(tmpID), bucket, retainSlash(object)) + if aErr != nil { + return toObjectErr(traceError(aErr), bucket, object) } } return nil diff --git a/cmd/xl-v1-list-objects-heal.go b/cmd/xl-v1-list-objects-heal.go index 1fb6702ac..94362d75c 100644 --- a/cmd/xl-v1-list-objects-heal.go +++ b/cmd/xl-v1-list-objects-heal.go @@ -17,6 +17,7 @@ package cmd import ( + "path/filepath" "sort" "strings" ) @@ -114,7 +115,7 @@ func (xl xlObjects) listObjectsHeal(bucket, prefix, marker, delimiter string, ma } entry := walkResult.entry var objInfo ObjectInfo - if strings.HasSuffix(entry, slashSeparator) { + if hasSuffix(entry, slashSeparator) { // Object name needs to be full path. objInfo.Bucket = bucket objInfo.Name = entry @@ -133,7 +134,7 @@ func (xl xlObjects) listObjectsHeal(bucket, prefix, marker, delimiter string, ma nextMarker = objInfo.Name objInfos = append(objInfos, objInfo) i++ - if walkResult.end == true { + if walkResult.end { eof = true break } @@ -205,3 +206,219 @@ func (xl xlObjects) ListObjectsHeal(bucket, prefix, marker, delimiter string, ma // Return error at the end. return ListObjectsInfo{}, toObjectErr(err, bucket, prefix) } + +// ListUploadsHeal - lists ongoing multipart uploads that require +// healing in one or more disks. +func (xl xlObjects) ListUploadsHeal(bucket, prefix, marker, uploadIDMarker, + delimiter string, maxUploads int) (ListMultipartsInfo, error) { + + // For delimiter and prefix as '/' we do not list anything at all + // since according to s3 spec we stop at the 'delimiter' along + // with the prefix. On a flat namespace with 'prefix' as '/' + // we don't have any entries, since all the keys are of form 'keyName/...' + if delimiter == slashSeparator && prefix == slashSeparator { + return ListMultipartsInfo{}, nil + } + + // Initiate a list operation. + listMultipartInfo, err := xl.listMultipartUploadsHeal(bucket, prefix, + marker, uploadIDMarker, delimiter, maxUploads) + if err != nil { + return ListMultipartsInfo{}, toObjectErr(err, bucket, prefix) + } + + // We got the entries successfully return. + return listMultipartInfo, nil +} + +// Fetches list of multipart uploadIDs given bucket, keyMarker, uploadIDMarker. +func fetchMultipartUploadIDs(bucket, keyMarker, uploadIDMarker string, + maxUploads int, disks []StorageAPI) (uploads []uploadMetadata, end bool, + err error) { + + // Hold a read lock on keyMarker path. + keyMarkerLock := globalNSMutex.NewNSLock(minioMetaMultipartBucket, + pathJoin(bucket, keyMarker)) + keyMarkerLock.RLock() + for _, disk := range disks { + if disk == nil { + continue + } + uploads, end, err = listMultipartUploadIDs(bucket, keyMarker, + uploadIDMarker, maxUploads, disk) + if err == nil || + !isErrIgnored(err, objMetadataOpIgnoredErrs...) { + break + } + } + keyMarkerLock.RUnlock() + return uploads, end, err +} + +// listMultipartUploadsHeal - Returns a list of incomplete multipart +// uploads that need to be healed. +func (xl xlObjects) listMultipartUploadsHeal(bucket, prefix, keyMarker, + uploadIDMarker, delimiter string, maxUploads int) (ListMultipartsInfo, error) { + + result := ListMultipartsInfo{ + IsTruncated: true, + MaxUploads: maxUploads, + KeyMarker: keyMarker, + Prefix: prefix, + Delimiter: delimiter, + } + + recursive := delimiter != slashSeparator + + var uploads []uploadMetadata + var err error + // List all upload ids for the given keyMarker, starting from + // uploadIDMarker. + if uploadIDMarker != "" { + uploads, _, err = fetchMultipartUploadIDs(bucket, keyMarker, + uploadIDMarker, maxUploads, xl.getLoadBalancedDisks()) + if err != nil { + return ListMultipartsInfo{}, err + } + maxUploads = maxUploads - len(uploads) + } + + // We can't use path.Join() as it strips off the trailing '/'. + multipartPrefixPath := pathJoin(bucket, prefix) + // multipartPrefixPath should have a trailing '/' when prefix = "". + if prefix == "" { + multipartPrefixPath += slashSeparator + } + + multipartMarkerPath := "" + if keyMarker != "" { + multipartMarkerPath = pathJoin(bucket, keyMarker) + } + + // `heal bool` is used to differentiate listing of incomplete + // uploads (and parts) from a regular listing of incomplete + // parts by client SDKs or mc-like commands, within a treewalk + // pool. + heal := true + // The listing is truncated if we have maxUploads entries and + // there are more entries to be listed. + truncated := true + var walkerCh chan treeWalkResult + var walkerDoneCh chan struct{} + // Check if we have room left to send more uploads. + if maxUploads > 0 { + walkerCh, walkerDoneCh = xl.listPool.Release(listParams{ + bucket: minioMetaMultipartBucket, + recursive: recursive, + marker: multipartMarkerPath, + prefix: multipartPrefixPath, + heal: heal, + }) + if walkerCh == nil { + walkerDoneCh = make(chan struct{}) + isLeaf := xl.isMultipartUpload + listDir := listDirFactory(isLeaf, xlTreeWalkIgnoredErrs, + xl.getLoadBalancedDisks()...) + walkerCh = startTreeWalk(minioMetaMultipartBucket, + multipartPrefixPath, multipartMarkerPath, + recursive, listDir, isLeaf, walkerDoneCh) + } + // Collect uploads until maxUploads limit is reached. + for walkResult := range walkerCh { + // Ignore errors like errDiskNotFound + // and errFileNotFound. + if isErrIgnored(walkResult.err, + xlTreeWalkIgnoredErrs...) { + continue + } + // For any error during tree walk we should + // return right away. + if walkResult.err != nil { + return ListMultipartsInfo{}, walkResult.err + } + + entry := strings.TrimPrefix(walkResult.entry, + retainSlash(bucket)) + // Skip entries that are not object directory. + if hasSuffix(walkResult.entry, slashSeparator) { + uploads = append(uploads, uploadMetadata{ + Object: entry, + }) + maxUploads-- + if maxUploads == 0 { + break + } + continue + } + + // For an object entry we get all its pending + // uploadIDs. + var newUploads []uploadMetadata + var end bool + uploadIDMarker = "" + newUploads, end, err = fetchMultipartUploadIDs(bucket, entry, uploadIDMarker, + maxUploads, xl.getLoadBalancedDisks()) + if err != nil { + return ListMultipartsInfo{}, err + } + uploads = append(uploads, newUploads...) + maxUploads -= len(newUploads) + if end && walkResult.end { + truncated = false + break + } + if maxUploads == 0 { + break + } + } + } + + // For all received uploads fill in the multiparts result. + for _, upload := range uploads { + var objectName string + var uploadID string + if hasSuffix(upload.Object, slashSeparator) { + // All directory entries are common + // prefixes. For common prefixes, upload ids + // are empty. + uploadID = "" + objectName = upload.Object + result.CommonPrefixes = append(result.CommonPrefixes, objectName) + } else { + // Check if upload needs healing. + uploadIDPath := filepath.Join(bucket, upload.Object, upload.UploadID) + partsMetadata, errs := readAllXLMetadata(xl.storageDisks, + minioMetaMultipartBucket, uploadIDPath) + if xlShouldHeal(partsMetadata, errs) { + healUploadInfo := xlHealStat(xl, partsMetadata, errs) + upload.HealUploadInfo = &healUploadInfo + result.Uploads = append(result.Uploads, upload) + } + uploadID = upload.UploadID + objectName = upload.Object + } + + result.NextKeyMarker = objectName + result.NextUploadIDMarker = uploadID + } + + if truncated { + // Put back the tree walk go-routine into the pool for + // subsequent use. + xl.listPool.Set(listParams{ + bucket: bucket, + recursive: recursive, + marker: result.NextKeyMarker, + prefix: prefix, + heal: heal, + }, walkerCh, walkerDoneCh) + } + + result.IsTruncated = truncated + // Result is not truncated, reset the markers. + if !result.IsTruncated { + result.NextKeyMarker = "" + result.NextUploadIDMarker = "" + } + return result, nil +} diff --git a/cmd/xl-v1-list-objects-heal_test.go b/cmd/xl-v1-list-objects-heal_test.go index fb515e325..af12060b2 100644 --- a/cmd/xl-v1-list-objects-heal_test.go +++ b/cmd/xl-v1-list-objects-heal_test.go @@ -18,6 +18,8 @@ package cmd import ( "bytes" + "path" + "path/filepath" "strconv" "testing" ) @@ -139,3 +141,78 @@ func TestListObjectsHeal(t *testing.T) { } } + +// Test for ListUploadsHeal API for XL. +func TestListUploadsHeal(t *testing.T) { + initNSLock(false) + + rootPath, err := newTestConfig(globalMinioDefaultRegion) + if err != nil { + t.Fatalf("Init Test config failed") + } + // Remove config directory after the test ends. + defer removeAll(rootPath) + + // Create an instance of XL backend. + xl, fsDirs, err := prepareXL() + if err != nil { + t.Fatal(err) + } + // Cleanup backend directories on function return. + defer removeRoots(fsDirs) + + bucketName := "bucket" + prefix := "prefix" + objName := path.Join(prefix, "obj") + + // Create test bucket. + err = xl.MakeBucket(bucketName) + if err != nil { + t.Fatal(err) + } + + // Create a new multipart upload. + uploadID, err := xl.NewMultipartUpload(bucketName, objName, nil) + if err != nil { + t.Fatal(err) + } + + // Upload a part. + data := bytes.Repeat([]byte("a"), 1024) + _, err = xl.PutObjectPart(bucketName, objName, uploadID, 1, + int64(len(data)), bytes.NewReader(data), "", "") + if err != nil { + t.Fatal(err) + } + + // Check if list uploads heal returns any uploads to be healed + // incorrectly. + listUploadsInfo, err := xl.ListUploadsHeal(bucketName, prefix, "", "", "", 1000) + if err != nil { + t.Fatal(err) + } + + // All uploads intact nothing to heal. + if len(listUploadsInfo.Uploads) != 0 { + t.Errorf("Expected no uploads but received %d", len(listUploadsInfo.Uploads)) + } + + // Delete the part from the first disk to make the upload (and + // its part) to appear in upload heal listing. + firstDisk := xl.(*xlObjects).storageDisks[0] + err = firstDisk.DeleteFile(minioMetaMultipartBucket, + filepath.Join(bucketName, objName, uploadID, xlMetaJSONFile)) + if err != nil { + t.Fatal(err) + } + + listUploadsInfo, err = xl.ListUploadsHeal(bucketName, prefix, "", "", "", 1000) + if err != nil { + t.Fatal(err) + } + + // One upload with missing xl.json on first disk. + if len(listUploadsInfo.Uploads) != 1 { + t.Errorf("Expected 1 upload but received %d", len(listUploadsInfo.Uploads)) + } +} diff --git a/cmd/xl-v1-list-objects.go b/cmd/xl-v1-list-objects.go index b9fff36ee..480f5359c 100644 --- a/cmd/xl-v1-list-objects.go +++ b/cmd/xl-v1-list-objects.go @@ -16,8 +16,6 @@ package cmd -import "strings" - // Returns function "listDir" of the type listDirFunc. // isLeaf - is used by listDir function to check if an entry is a leaf or non-leaf entry. // disks - used for doing disk.ListDir(). FS passes single disk argument, XL passes a list of disks. @@ -83,7 +81,7 @@ func (xl xlObjects) listObjects(bucket, prefix, marker, delimiter string, maxKey } entry := walkResult.entry var objInfo ObjectInfo - if strings.HasSuffix(entry, slashSeparator) { + if hasSuffix(entry, slashSeparator) { // Object name needs to be full path. objInfo.Bucket = bucket objInfo.Name = entry diff --git a/cmd/xl-v1-metadata.go b/cmd/xl-v1-metadata.go index 243dab9fe..29613e480 100644 --- a/cmd/xl-v1-metadata.go +++ b/cmd/xl-v1-metadata.go @@ -361,6 +361,8 @@ func writeUniqueXLMetadata(disks []StorageAPI, bucket, prefix string, xlMetas [] err := writeXLMetadata(disk, bucket, prefix, xlMetas[index]) if err != nil { mErrs[index] = err + // Ignore disk which returned an error. + disks[index] = nil } }(index, disk) } @@ -399,6 +401,8 @@ func writeSameXLMetadata(disks []StorageAPI, bucket, prefix string, xlMeta xlMet err := writeXLMetadata(disk, bucket, prefix, metadata) if err != nil { mErrs[index] = err + // Ignore disk which returned an error. + disks[index] = nil } }(index, disk, xlMeta) } diff --git a/cmd/xl-v1-multipart.go b/cmd/xl-v1-multipart.go index 51624784c..a6816dbe4 100644 --- a/cmd/xl-v1-multipart.go +++ b/cmd/xl-v1-multipart.go @@ -254,6 +254,8 @@ func commitXLMetadata(disks []StorageAPI, srcBucket, srcPrefix, dstBucket, dstPr rErr := disk.RenameFile(srcBucket, srcJSONFile, dstBucket, dstJSONFile) if rErr != nil { mErrs[index] = traceError(rErr) + // Ignore disk which returned an error. + disks[index] = nil return } mErrs[index] = nil @@ -356,7 +358,7 @@ func (xl xlObjects) listMultipartUploads(bucket, prefix, keyMarker, uploadIDMark entry := strings.TrimPrefix(walkResult.entry, retainSlash(bucket)) // For an entry looking like a directory, store and // continue the loop not need to fetch uploads. - if strings.HasSuffix(walkResult.entry, slashSeparator) { + if hasSuffix(walkResult.entry, slashSeparator) { uploads = append(uploads, uploadMetadata{ Object: entry, }) @@ -409,7 +411,7 @@ func (xl xlObjects) listMultipartUploads(bucket, prefix, keyMarker, uploadIDMark for _, upload := range uploads { var objectName string var uploadID string - if strings.HasSuffix(upload.Object, slashSeparator) { + if hasSuffix(upload.Object, slashSeparator) { // All directory entries are common prefixes. uploadID = "" // For common prefixes, upload ids are empty. objectName = upload.Object @@ -499,15 +501,15 @@ func (xl xlObjects) newMultipartUpload(bucket string, object string, meta map[st // success. defer xl.deleteObject(minioMetaTmpBucket, tempUploadIDPath) - // Attempt to rename temp upload object to actual upload path - // object - if rErr := renameObject(xl.storageDisks, minioMetaTmpBucket, tempUploadIDPath, minioMetaMultipartBucket, uploadIDPath, xl.writeQuorum); rErr != nil { + // Attempt to rename temp upload object to actual upload path object + rErr := renameObject(xl.storageDisks, minioMetaTmpBucket, tempUploadIDPath, minioMetaMultipartBucket, uploadIDPath, xl.writeQuorum) + if rErr != nil { return "", toObjectErr(rErr, minioMetaMultipartBucket, uploadIDPath) } initiated := time.Now().UTC() // Create or update 'uploads.json' - if err := xl.addUploadID(bucket, object, uploadID, initiated); err != nil { + if err = xl.addUploadID(bucket, object, uploadID, initiated); err != nil { return "", err } // Return success. @@ -544,7 +546,6 @@ func (xl xlObjects) CopyObjectPart(srcBucket, srcObject, dstBucket, dstObject, u pipeReader, pipeWriter := io.Pipe() go func() { - startOffset := int64(0) // Read the whole file. if gerr := xl.GetObject(srcBucket, srcObject, startOffset, length, pipeWriter); gerr != nil { errorIf(gerr, "Unable to read %s of the object `%s/%s`.", srcBucket, srcObject) pipeWriter.CloseWithError(toObjectErr(gerr, srcBucket, srcObject)) @@ -607,8 +608,7 @@ func (xl xlObjects) PutObjectPart(bucket, object, uploadID string, partID int, s return PartInfo{}, err } - onlineDisks = getOrderedDisks(xlMeta.Erasure.Distribution, onlineDisks) - _ = getOrderedPartsMetadata(xlMeta.Erasure.Distribution, partsMetadata) + onlineDisks = shuffleDisks(onlineDisks, xlMeta.Erasure.Distribution) // Need a unique name for the part being written in minioMetaBucket to // accommodate concurrent PutObjectPart requests @@ -648,11 +648,9 @@ func (xl xlObjects) PutObjectPart(bucket, object, uploadID string, partID int, s defer xl.deleteObject(minioMetaTmpBucket, tmpPart) if size > 0 { - for _, disk := range onlineDisks { - if disk != nil { - actualSize := xl.sizeOnDisk(size, xlMeta.Erasure.BlockSize, xlMeta.Erasure.DataBlocks) - disk.PrepareFile(minioMetaTmpBucket, tmpPartPath, actualSize) - } + if pErr := xl.prepareFile(minioMetaTmpBucket, tmpPartPath, size, onlineDisks, xlMeta.Erasure.BlockSize, xlMeta.Erasure.DataBlocks); err != nil { + return PartInfo{}, toObjectErr(pErr, bucket, object) + } } @@ -885,6 +883,13 @@ func (xl xlObjects) CompleteMultipartUpload(bucket string, object string, upload if !xl.isUploadIDExists(bucket, object, uploadID) { return ObjectInfo{}, traceError(InvalidUploadID{UploadID: uploadID}) } + + // Check if an object is present as one of the parent dir. + // -- FIXME. (needs a new kind of lock). + if xl.parentDirIsObject(bucket, path.Dir(object)) { + return ObjectInfo{}, toObjectErr(traceError(errFileAccessDenied), bucket, object) + } + // Calculate s3 compatible md5sum for complete multipart. s3MD5, err := getCompleteMultipartMD5(parts) if err != nil { @@ -912,10 +917,10 @@ func (xl xlObjects) CompleteMultipartUpload(bucket string, object string, upload } // Order online disks in accordance with distribution order. - onlineDisks = getOrderedDisks(xlMeta.Erasure.Distribution, onlineDisks) + onlineDisks = shuffleDisks(onlineDisks, xlMeta.Erasure.Distribution) // Order parts metadata in accordance with distribution order. - partsMetadata = getOrderedPartsMetadata(xlMeta.Erasure.Distribution, partsMetadata) + partsMetadata = shufflePartsMetadata(partsMetadata, xlMeta.Erasure.Distribution) // Save current xl meta for validation. var currentXLMeta = xlMeta @@ -964,11 +969,6 @@ func (xl xlObjects) CompleteMultipartUpload(bucket string, object string, upload } } - // Check if an object is present as one of the parent dir. - if xl.parentDirIsObject(bucket, path.Dir(object)) { - return ObjectInfo{}, toObjectErr(traceError(errFileAccessDenied), bucket, object) - } - // Save the final object size and modtime. xlMeta.Stat.Size = objectSize xlMeta.Stat.ModTime = time.Now().UTC() @@ -990,6 +990,7 @@ func (xl xlObjects) CompleteMultipartUpload(bucket string, object string, upload if err = writeUniqueXLMetadata(onlineDisks, minioMetaTmpBucket, tempUploadIDPath, partsMetadata, xl.writeQuorum); err != nil { return ObjectInfo{}, toObjectErr(err, minioMetaTmpBucket, tempUploadIDPath) } + rErr := commitXLMetadata(onlineDisks, minioMetaTmpBucket, tempUploadIDPath, minioMetaMultipartBucket, uploadIDPath, xl.writeQuorum) if rErr != nil { return ObjectInfo{}, toObjectErr(rErr, minioMetaMultipartBucket, uploadIDPath) @@ -1008,13 +1009,17 @@ func (xl xlObjects) CompleteMultipartUpload(bucket string, object string, upload } }() - // Rename if an object already exists to temporary location. - uniqueID := mustGetUUID() if xl.isObject(bucket, object) { + // Rename if an object already exists to temporary location. + newUniqueID := mustGetUUID() + + // Delete success renamed object. + defer xl.deleteObject(minioMetaTmpBucket, newUniqueID) + // NOTE: Do not use online disks slice here. // The reason is that existing object should be purged // regardless of `xl.json` status and rolled back in case of errors. - err = renameObject(xl.storageDisks, bucket, object, minioMetaTmpBucket, uniqueID, xl.writeQuorum) + err = renameObject(xl.storageDisks, bucket, object, minioMetaTmpBucket, newUniqueID, xl.writeQuorum) if err != nil { return ObjectInfo{}, toObjectErr(err, bucket, object) } @@ -1038,9 +1043,6 @@ func (xl xlObjects) CompleteMultipartUpload(bucket string, object string, upload return ObjectInfo{}, toObjectErr(err, bucket, object) } - // Delete the previously successfully renamed object. - xl.deleteObject(minioMetaTmpBucket, uniqueID) - // Hold the lock so that two parallel // complete-multipart-uploads do not leave a stale // uploads.json behind. diff --git a/cmd/xl-v1-object.go b/cmd/xl-v1-object.go index 1ac3f23fe..f7ecf76c9 100644 --- a/cmd/xl-v1-object.go +++ b/cmd/xl-v1-object.go @@ -36,6 +36,25 @@ import ( // list all errors which can be ignored in object operations. var objectOpIgnoredErrs = append(baseIgnoredErrs, errDiskAccessDenied) +// prepareFile hints the bottom layer to optimize the creation of a new object +func (xl xlObjects) prepareFile(bucket, object string, size int64, onlineDisks []StorageAPI, blockSize int64, dataBlocks int) error { + pErrs := make([]error, len(onlineDisks)) + // Calculate the real size of the part in one disk. + actualSize := xl.sizeOnDisk(size, blockSize, dataBlocks) + // Prepare object creation in a all disks + for index, disk := range onlineDisks { + if disk != nil { + if err := disk.PrepareFile(bucket, object, actualSize); err != nil { + // Save error to reduce it later + pErrs[index] = err + // Ignore later access to disk which generated the error + onlineDisks[index] = nil + } + } + } + return reduceWriteQuorumErrs(pErrs, objectOpIgnoredErrs, xl.writeQuorum) +} + /// Object Operations // CopyObject - copy object source object to destination object. @@ -58,13 +77,13 @@ func (xl xlObjects) CopyObject(srcBucket, srcObject, dstBucket, dstObject string } // Reorder online disks based on erasure distribution order. - onlineDisks = getOrderedDisks(xlMeta.Erasure.Distribution, onlineDisks) + onlineDisks = shuffleDisks(onlineDisks, xlMeta.Erasure.Distribution) // Length of the file to read. length := xlMeta.Stat.Size // Check if this request is only metadata update. - cpMetadataOnly := strings.EqualFold(pathJoin(srcBucket, srcObject), pathJoin(dstBucket, dstObject)) + cpMetadataOnly := isStringEqual(pathJoin(srcBucket, srcObject), pathJoin(dstBucket, dstObject)) if cpMetadataOnly { xlMeta.Meta = metadata partsMetadata := make([]xlMetaV1, len(xl.storageDisks)) @@ -106,7 +125,7 @@ func (xl xlObjects) CopyObject(srcBucket, srcObject, dstBucket, dstObject string pipeReader, pipeWriter := io.Pipe() go func() { - startOffset := int64(0) // Read the whole file. + var startOffset int64 // Read the whole file. if gerr := xl.GetObject(srcBucket, srcObject, startOffset, length, pipeWriter); gerr != nil { errorIf(gerr, "Unable to read %s of the object `%s/%s`.", srcBucket, srcObject) pipeWriter.CloseWithError(toObjectErr(gerr, srcBucket, srcObject)) @@ -163,10 +182,10 @@ func (xl xlObjects) GetObject(bucket, object string, startOffset int64, length i } // Reorder online disks based on erasure distribution order. - onlineDisks = getOrderedDisks(xlMeta.Erasure.Distribution, onlineDisks) + onlineDisks = shuffleDisks(onlineDisks, xlMeta.Erasure.Distribution) // Reorder parts metadata based on erasure distribution order. - metaArr = getOrderedPartsMetadata(xlMeta.Erasure.Distribution, metaArr) + metaArr = shufflePartsMetadata(metaArr, xlMeta.Erasure.Distribution) // For negative length read everything. if length < 0 { @@ -242,7 +261,7 @@ func (xl xlObjects) GetObject(bucket, object string, startOffset int64, length i } } - totalBytesRead := int64(0) + var totalBytesRead int64 chunkSize := getChunkSize(xlMeta.Erasure.BlockSize, xlMeta.Erasure.DataBlocks) pool := bpool.NewBytePool(chunkSize, len(onlineDisks)) @@ -302,6 +321,12 @@ func (xl xlObjects) GetObject(bucket, object string, startOffset int64, length i // GetObjectInfo - reads object metadata and replies back ObjectInfo. func (xl xlObjects) GetObjectInfo(bucket, object string) (ObjectInfo, error) { + // This is a special case with object whose name ends with + // a slash separator, we always return object not found here. + if hasSuffix(object, slashSeparator) { + return ObjectInfo{}, toObjectErr(traceError(errFileNotFound), bucket, object) + } + if err := checkGetObjArgs(bucket, object); err != nil { return ObjectInfo{}, err } @@ -395,6 +420,8 @@ func rename(disks []StorageAPI, srcBucket, srcEntry, dstBucket, dstEntry string, err := disk.RenameFile(srcBucket, srcEntry, dstBucket, dstEntry) if err != nil && err != errFileNotFound { errs[index] = traceError(err) + // Ignore disk which returned an error. + disks[index] = nil } }(index, disk) } @@ -439,11 +466,25 @@ func (xl xlObjects) PutObject(bucket string, object string, size int64, data io. // a slash separator, we treat it like a valid operation and // return success. if isObjectDir(object, size) { + // Check if an object is present as one of the parent dir. + // -- FIXME. (needs a new kind of lock). + if xl.parentDirIsObject(bucket, path.Dir(object)) { + return ObjectInfo{}, toObjectErr(traceError(errFileAccessDenied), bucket, object) + } return dirObjectInfo(bucket, object, size, metadata), nil } + + // Validate put object input args. if err = checkPutObjectArgs(bucket, object, xl); err != nil { return ObjectInfo{}, err } + + // Check if an object is present as one of the parent dir. + // -- FIXME. (needs a new kind of lock). + if xl.parentDirIsObject(bucket, path.Dir(object)) { + return ObjectInfo{}, toObjectErr(traceError(errFileAccessDenied), bucket, object) + } + // No metadata is set, allocate a new one. if metadata == nil { metadata = make(map[string]string) @@ -511,7 +552,7 @@ func (xl xlObjects) PutObject(bucket string, object string, size int64, data io. } // Order disks according to erasure distribution - onlineDisks := getOrderedDisks(partsMetadata[0].Erasure.Distribution, xl.storageDisks) + onlineDisks := shuffleDisks(xl.storageDisks, partsMetadata[0].Erasure.Distribution) // Delete temporary object in the event of failure. // If PutObject succeeded there would be no temporary @@ -519,14 +560,14 @@ func (xl xlObjects) PutObject(bucket string, object string, size int64, data io. defer xl.deleteObject(minioMetaTmpBucket, tempObj) // Total size of the written object - sizeWritten := int64(0) + var sizeWritten int64 // Read data and split into parts - similar to multipart mechanism for partIdx := 1; ; partIdx++ { // Compute part name partName := "part." + strconv.Itoa(partIdx) // Compute the path of current part - tempErasureObj := path.Join(uniqueID, partName) + tempErasureObj := pathJoin(uniqueID, partName) // Calculate the size of the current part, if size is unknown, curPartSize wil be unknown too. // allowEmptyPart will always be true if this is the first part and false otherwise. @@ -536,14 +577,12 @@ func (xl xlObjects) PutObject(bucket string, object string, size int64, data io. return ObjectInfo{}, toObjectErr(err, bucket, object) } - // Prepare file for eventual optimization in the disk + // Hint the filesystem to pre-allocate one continuous large block. + // This is only an optimization. if curPartSize > 0 { - // Calculate the real size of the part in the disk and prepare it for eventual optimization - actualSize := xl.sizeOnDisk(curPartSize, xlMeta.Erasure.BlockSize, xlMeta.Erasure.DataBlocks) - for _, disk := range onlineDisks { - if disk != nil { - disk.PrepareFile(minioMetaTmpBucket, tempErasureObj, actualSize) - } + pErr := xl.prepareFile(minioMetaTmpBucket, tempErasureObj, curPartSize, onlineDisks, xlMeta.Erasure.BlockSize, xlMeta.Erasure.DataBlocks) + if pErr != nil { + return ObjectInfo{}, toObjectErr(pErr, bucket, object) } } @@ -645,16 +684,11 @@ func (xl xlObjects) PutObject(bucket string, object string, size int64, data io. } } - // Check if an object is present as one of the parent dir. - // -- FIXME. (needs a new kind of lock). - if xl.parentDirIsObject(bucket, path.Dir(object)) { - return ObjectInfo{}, toObjectErr(traceError(errFileAccessDenied), bucket, object) - } - - // Rename if an object already exists to temporary location. - newUniqueID := mustGetUUID() if xl.isObject(bucket, object) { - // Delete the temporary copy of the object that existed before this PutObject request. + // Rename if an object already exists to temporary location. + newUniqueID := mustGetUUID() + + // Delete successfully renamed object. defer xl.deleteObject(minioMetaTmpBucket, newUniqueID) // NOTE: Do not use online disks slice here. diff --git a/cmd/xl-v1-object_test.go b/cmd/xl-v1-object_test.go index 1349ec985..0c30cd355 100644 --- a/cmd/xl-v1-object_test.go +++ b/cmd/xl-v1-object_test.go @@ -265,6 +265,12 @@ func TestPutObjectNoQuorum(t *testing.T) { // Tests both object and bucket healing. func TestHealing(t *testing.T) { + rootPath, err := newTestConfig(globalMinioDefaultRegion) + if err != nil { + t.Fatalf("Failed to initialize test config %v", err) + } + defer removeAll(rootPath) + obj, fsDirs, err := prepareXL() if err != nil { t.Fatal(err) diff --git a/cmd/xl-v1-utils.go b/cmd/xl-v1-utils.go index ecedbad1e..4faf67d74 100644 --- a/cmd/xl-v1-utils.go +++ b/cmd/xl-v1-utils.go @@ -306,26 +306,34 @@ func readAllXLMetadata(disks []StorageAPI, bucket, object string) ([]xlMetaV1, [ return metadataArray, errs } -// Return ordered partsMetadata depending on distribution. -func getOrderedPartsMetadata(distribution []int, partsMetadata []xlMetaV1) (orderedPartsMetadata []xlMetaV1) { - orderedPartsMetadata = make([]xlMetaV1, len(partsMetadata)) +// Return shuffled partsMetadata depending on distribution. +func shufflePartsMetadata(partsMetadata []xlMetaV1, distribution []int) (shuffledPartsMetadata []xlMetaV1) { + if distribution == nil { + return partsMetadata + } + shuffledPartsMetadata = make([]xlMetaV1, len(partsMetadata)) + // Shuffle slice xl metadata for expected distribution. for index := range partsMetadata { blockIndex := distribution[index] - orderedPartsMetadata[blockIndex-1] = partsMetadata[index] + shuffledPartsMetadata[blockIndex-1] = partsMetadata[index] } - return orderedPartsMetadata + return shuffledPartsMetadata } -// getOrderedDisks - get ordered disks from erasure distribution. -// returns ordered slice of disks from their actual distribution. -func getOrderedDisks(distribution []int, disks []StorageAPI) (orderedDisks []StorageAPI) { - orderedDisks = make([]StorageAPI, len(disks)) - // From disks gets ordered disks. +// shuffleDisks - shuffle input disks slice depending on the +// erasure distribution. return shuffled slice of disks with +// their expected distribution. +func shuffleDisks(disks []StorageAPI, distribution []int) (shuffledDisks []StorageAPI) { + if distribution == nil { + return disks + } + shuffledDisks = make([]StorageAPI, len(disks)) + // Shuffle disks for expected distribution. for index := range disks { blockIndex := distribution[index] - orderedDisks[blockIndex-1] = disks[index] + shuffledDisks[blockIndex-1] = disks[index] } - return orderedDisks + return shuffledDisks } // Errors specifically generated by getPartSizeFromIdx function. diff --git a/cmd/xl-v1-utils_test.go b/cmd/xl-v1-utils_test.go index bf6f2205b..80901b703 100644 --- a/cmd/xl-v1-utils_test.go +++ b/cmd/xl-v1-utils_test.go @@ -383,3 +383,51 @@ func TestGetPartSizeFromIdx(t *testing.T) { } } } + +func TestShuffleDisks(t *testing.T) { + nDisks := 16 + disks, err := getRandomDisks(nDisks) + if err != nil { + t.Fatal(err) + } + endpoints, err := parseStorageEndpoints(disks) + if err != nil { + t.Fatal(err) + } + objLayer, _, err := initObjectLayer(endpoints) + if err != nil { + removeRoots(disks) + t.Fatal(err) + } + defer removeRoots(disks) + xl := objLayer.(*xlObjects) + testShuffleDisks(t, xl) +} + +// Test shuffleDisks which returns shuffled slice of disks for their actual distribution. +func testShuffleDisks(t *testing.T, xl *xlObjects) { + disks := xl.storageDisks + distribution := []int{16, 14, 12, 10, 8, 6, 4, 2, 1, 3, 5, 7, 9, 11, 13, 15} + shuffledDisks := shuffleDisks(disks, distribution) + // From the "distribution" above you can notice that: + // 1st data block is in the 9th disk (i.e distribution index 8) + // 2nd data block is in the 8th disk (i.e distribution index 7) and so on. + if shuffledDisks[0] != disks[8] || + shuffledDisks[1] != disks[7] || + shuffledDisks[2] != disks[9] || + shuffledDisks[3] != disks[6] || + shuffledDisks[4] != disks[10] || + shuffledDisks[5] != disks[5] || + shuffledDisks[6] != disks[11] || + shuffledDisks[7] != disks[4] || + shuffledDisks[8] != disks[12] || + shuffledDisks[9] != disks[3] || + shuffledDisks[10] != disks[13] || + shuffledDisks[11] != disks[2] || + shuffledDisks[12] != disks[14] || + shuffledDisks[13] != disks[1] || + shuffledDisks[14] != disks[15] || + shuffledDisks[15] != disks[0] { + t.Errorf("shuffleDisks returned incorrect order.") + } +} diff --git a/cmd/xl-v1.go b/cmd/xl-v1.go index 4f6083d75..f58cb9182 100644 --- a/cmd/xl-v1.go +++ b/cmd/xl-v1.go @@ -127,14 +127,23 @@ func newXLObjects(storageDisks []StorageAPI) (ObjectLayer, error) { listPool: listPool, } - // Object cache is enabled when _MINIO_CACHE env is missing. - // and cache size is > 0. - xl.objCacheEnabled = !objCacheDisabled && globalMaxCacheSize > 0 + // Get cache size if _MINIO_CACHE environment variable is set. + var maxCacheSize uint64 + if !objCacheDisabled { + maxCacheSize, err = GetMaxCacheSize() + errorIf(err, "Unable to get maximum cache size") + + // Enable object cache if cache size is more than zero + xl.objCacheEnabled = maxCacheSize > 0 + } // Check if object cache is enabled. if xl.objCacheEnabled { // Initialize object cache. - objCache := objcache.New(globalMaxCacheSize, globalCacheExpiry) + objCache, oerr := objcache.New(maxCacheSize, objcache.DefaultExpiry) + if oerr != nil { + return nil, oerr + } objCache.OnEviction = func(key string) { debug.FreeOSMemory() } @@ -142,7 +151,7 @@ func newXLObjects(storageDisks []StorageAPI) (ObjectLayer, error) { } // Initialize meta volume, if volume already exists ignores it. - if err = initMetaVolume(storageDisks); err != nil { + if err = initMetaVolume(xl.storageDisks); err != nil { return nil, fmt.Errorf("Unable to initialize '.minio.sys' meta volume, %s", err) } @@ -152,12 +161,7 @@ func newXLObjects(storageDisks []StorageAPI) (ObjectLayer, error) { xl.writeQuorum = writeQuorum // Do a quick heal on the buckets themselves for any discrepancies. - if err := quickHeal(xl.storageDisks, xl.writeQuorum, xl.readQuorum); err != nil { - return xl, err - } - - // Return successfully initialized object layer. - return xl, nil + return xl, quickHeal(xl.storageDisks, xl.writeQuorum, xl.readQuorum) } // Shutdown function for object storage interface. diff --git a/docs/bucket/notifications/README.md b/docs/bucket/notifications/README.md index 82e299342..27d5fd43f 100644 --- a/docs/bucket/notifications/README.md +++ b/docs/bucket/notifications/README.md @@ -1,6 +1,7 @@ # Minio Bucket Notification Guide [![Slack](https://slack.minio.io/slack?type=svg)](https://slack.minio.io) -Minio server supports Amazon S3 compatible bucket event notification for the following targets +Changes in a bucket, such as object uploads and removal, can be monitored using bucket event notification +mechanism and can be published to the following targets: | Notification Targets| |:---| @@ -460,7 +461,7 @@ arn:minio:sqs:us-east-1:1:kafka s3:ObjectCreated:*,s3:ObjectRemoved:* Filter: su We used [kafkacat](https://github.com/edenhill/kafkacat) to print all notifications on the console. ``` -kafkacat -b localhost:9092 -t bucketevents +kafkacat -C -b localhost:9092 -t bucketevents ``` Open another terminal and upload a JPEG image into ``images`` bucket. diff --git a/docs/distributed/README.md b/docs/distributed/README.md index de8ba314b..59dafe7af 100644 --- a/docs/distributed/README.md +++ b/docs/distributed/README.md @@ -39,9 +39,11 @@ Install Minio - [Minio Quickstart Guide](https://docs.minio.io/docs/minio-quicks To start a distributed Minio instance, you just need to pass drive locations as parameters to the minio server command. Then, you’ll need to run the same command on all the participating nodes. *Note* + - All the nodes running distributed Minio need to have same access key and secret key for the nodes to connect. To achieve this, you need to export access key and secret key as environment variables on all the nodes before executing Minio server command. - Disks used for Minio distributed should be fresh with no pre-existing data. - The IP addresses and drive paths below are for demonstration purposes only, you need to replace these with the actual IP addresses and drive paths/folders. +- Servers running distributed Minio instances should be less than 3 seconds apart. You can use [NTP](http://www.ntp.org/) as a best practice to ensure consistent times across servers. Example 1: Start distributed Minio instance with 1 drive each on 8 nodes, by running this command on all the 8 nodes. diff --git a/docs/erasure/README.md b/docs/erasure/README.md index 9016026c7..48cc12a29 100644 --- a/docs/erasure/README.md +++ b/docs/erasure/README.md @@ -8,27 +8,27 @@ Install Minio - [Minio Quickstart Guide](https://docs.minio.io/docs/minio-quicks ## What is Erasure Code? -Erasure code is a mathematical algorithm to reconstruct missing or corrupted data. Minio uses Reed-Solomon code to shard objects into N/2 data and N/2 parity blocks. This means that in a 12 drive setup, an object is sharded across as 6 data and 6 parity blocks. You can lose as many as 6 drives (be it parity or data) and still reconstruct the data reliably from the remaining drives. +Erasure code is a mathematical algorithm to reconstruct missing or corrupted data. Minio uses Reed-Solomon code to shard objects into N/2 data and N/2 parity blocks. This means that in a 12 drive setup, an object is sharded across as 6 data and 6 parity blocks. You can lose as many as 6 drives (be it parity or data) and still reconstruct the data reliably from the remaining drives. ## Why is Erasure Code useful? Erasure code protects data from multiple drives failure unlike RAID or replication. For eg RAID6 can protect against 2 drive failure whereas in Minio erasure code you can lose as many as half number of drives and still the data remains safe. Further Minio's erasure code is at object level and can heal one object at a time. For RAID, healing can only be performed at volume level which translates into huge down time. As Minio encodes each object individually with a high parity count. Storage servers once deployed should not require drive replacement or healing for the lifetime of the server. Minio's erasure coded backend is designed for operational efficiency and takes full advantage of hardware acceleration whenever available. -![Erasure](https://raw.githubusercontent.com/minio/minio/master/docs/screenshots/erasure-code.jpg?raw=true) +![Erasure](https://raw.githubusercontent.com/minio/minio/master/docs/screenshots/erasure-code.png?raw=true) ## What is Bit Rot protection? -Bit Rot also known as Data Rot or Silent Data Corruption is a serious data loss issue faced by disk drives today. Data on the drive may silently get corrupted without signalling an error has occurred. This makes Bit Rot more dangerous than permanent hard drive failure. +Bit Rot also known as Data Rot or Silent Data Corruption is a serious data loss issue faced by disk drives today. Data on the drive may silently get corrupted without signalling an error has occurred. This makes Bit Rot more dangerous than permanent hard drive failure. Minio's erasure coded backend uses high speed [BLAKE2](https://blog.minio.io/accelerating-blake2b-by-4x-using-simd-in-go-assembly-33ef16c8a56b#.jrp1fdwer) hash based checksums to protect against Bit Rot. ## Deployment Scenarios -Minio server runs on a variety of hardware, operating systems and virtual/container environments. +Minio server runs on a variety of hardware, operating systems and virtual/container environments. -Minio erasure code backend is limited by design to a minimum of 4 drives and a maximum of 16 drives. The hard limit of 16 drives comes from operational experience. Failure domain becomes too large beyond 16 drives. If you need to scale beyond 16 drives, you may run multiple instances of Minio server on different ports. +Minio erasure code backend is limited by design to a minimum of 4 drives and a maximum of 16 drives. The hard limit of 16 drives comes from operational experience. Failure domain becomes too large beyond 16 drives. If you need to scale beyond 16 drives, you may run multiple instances of Minio server on different ports. -#### Reference Physical Hardware: +#### Reference Physical Hardware: * [SMC 5018A-AR12L (Intel Atom)](http://www.supermicro.com/products/system/1U/5018/SSG-5018A-AR12L.cfm?parts=SHOW) - SMC 1U SoC Atom C2750 platform with 12x 3.5” drive bays * [Quanta Grid D51B-2U (OCP Compliant) ](http://www.qct.io/Product/Servers/Rackmount-Servers/2U/QuantaGrid-D51B-2U-p256c77c70c83c118)- Quanta 2U DP E5-2600v3 platform with 12x 3.5” drive bays diff --git a/docs/gateway/README.md b/docs/gateway/README.md new file mode 100644 index 000000000..51f54e583 --- /dev/null +++ b/docs/gateway/README.md @@ -0,0 +1,48 @@ +# Minio Gateway [![Slack](https://slack.minio.io/slack?type=svg)](https://slack.minio.io) + +Minio gateway adds Amazon S3 compatibility to third party cloud storage providers. Supported providers are: + +- Azure Blob Storage + +## Run Minio Gateway for Azure Blob Storage + +### Using Docker + +``` +docker run -p 9000:9000 --name azure-s3 \ + -e "MINIO_ACCESS_KEY=azureaccountname" \ + -e "MINIO_SECRET_KEY=azureaccountkey" \ + minio/minio gateway azure +``` + +### Using Binary + +``` +export MINIO_ACCESS_KEY=azureaccountname +export MINIO_SECRET_KEY=azureaccountkey +minio gateway azure +``` + +## Test using Minio Client `mc` +`mc` provides a modern alternative to UNIX commands such as ls, cat, cp, mirror, diff etc. It supports filesystems and Amazon S3 compatible cloud storage services. + +### Configure `mc` + +``` +mc config host add myazure http://gateway-ip:9000 azureaccountname azureaccountkey +``` + +### List containers on Azure + +``` +mc ls myazure +[2017-02-22 01:50:43 PST] 0B ferenginar/ +[2017-02-26 21:43:51 PST] 0B my-container/ +[2017-02-26 22:10:11 PST] 0B test-container1/ +``` + +## Explore Further +- [`mc` command-line interface](https://docs.minio.io/docs/minio-client-quickstart-guide) +- [`aws` command-line interface](https://docs.minio.io/docs/aws-cli-with-minio) +- [`minfs` filesystem interface](http://docs.minio.io/docs/minfs-quickstart-guide) +- [`minio-go` Go SDK](https://docs.minio.io/docs/golang-client-quickstart-guide) diff --git a/docs/gateway/azure-limitations.md b/docs/gateway/azure-limitations.md new file mode 100644 index 000000000..7108bafa3 --- /dev/null +++ b/docs/gateway/azure-limitations.md @@ -0,0 +1,19 @@ +## Minio Azure Gateway Limitations + +Gateway inherits the following Azure limitations: + +- Maximum Multipart part size is 100MB. +- Maximum Multipart object size is 10000*100 MB = 1TB +- No support for prefix based bucket policies. Only top level bucket policy is supported. +- Gateway restart implies all the ongoing multipart uploads must be restarted. + i.e clients must again start with NewMultipartUpload + This is because S3 clients send metadata in NewMultipartUpload but Azure expects metadata to + be set during CompleteMultipartUpload (PutBlockList in Azure terminology). We store the metadata + sent by the client during NewMultipartUpload in memory so that it can be set on Azure later during + CompleteMultipartUpload. When the gateway is restarted this information is lost. +- Bucket names with "." in the bucket name is not supported. +- Non-empty buckets get removed on a DeleteBucket() call. + +Other limitations: +- Current implementation of ListMultipartUploads is incomplete. Right now it returns if the object with name "prefix" has any uploaded parts. +- Bucket notification not supported. diff --git a/docs/orchestration/README.md b/docs/orchestration/README.md new file mode 100644 index 000000000..b5a264579 --- /dev/null +++ b/docs/orchestration/README.md @@ -0,0 +1,22 @@ +# Minio Deployment Quickstart Guide [![Slack](https://slack.minio.io/slack?type=svg)](https://slack.minio.io) [![Go Report Card](https://goreportcard.com/badge/minio/minio)](https://goreportcard.com/report/minio/minio) [![Docker Pulls](https://img.shields.io/docker/pulls/minio/minio.svg?maxAge=604800)](https://hub.docker.com/r/minio/minio/) [![codecov](https://codecov.io/gh/minio/minio/branch/master/graph/badge.svg)](https://codecov.io/gh/minio/minio) + +Minio is a cloud-native application designed to scale in a sustainable manner in multi-tenant environments. Orchestration platforms provide perfect launchpad for Minio to scale. Below is the list of Minio deployment documents for various orchestration platforms: + +| Orchestration platforms| +|:---| +| [`Docker Swarm`](http://docs.minio.io/docs/deploy-minio-on-docker-swarm) | +| [`Kubernetes`](http://docs.minio.io/docs/deploy-minio-on-kubernetes) | +| [`DC/OS`](http://docs.minio.io/docs/deploy-minio-on-dc-os) | + +## Why is Minio cloud-native? +The term cloud-native revolves around the idea of applications deployed as micro services, that scale well. It is not about just retrofitting monolithic applications onto modern container based compute environment. A cloud-native application is portable and resilient by design, and can scale horizontally by simply replicating. Modern orchestration platforms like Swarm, Kubernetes and DC/OS make replicating and managing containers in huge clusters easier than ever. + +While containers provide isolated application execution environment, orchestration platforms allow seamless scaling by helping replicate and manage containers. Minio extends this by adding isolated storage environment for each tenant. + +Minio is built ground up on the cloud-native premise. With features like erasure-coding, distributed and shared setup, it focusses only on storage and does it very well. While, it can be scaled by just replicating Minio instances per tenant via an orchestration platform. + +> In a cloud-native environment, scalability is not a function of the application but the orchestration platform. + +In a typical modern infrastructure deployment, application, database, key-store, etc. already live in containers and are managed by orchestration platforms. Minio brings robust, scalable, AWS S3 compatible object storage to the lot. + +![Cloud-native](https://raw.githubusercontent.com/NitishT/minio/master/docs/screenshots/Minio_Cloud_Native_Arch.png?raw=true) diff --git a/docs/orchestration/dcos/README.md b/docs/orchestration/dcos/README.md new file mode 100644 index 000000000..55d14e8a6 --- /dev/null +++ b/docs/orchestration/dcos/README.md @@ -0,0 +1,43 @@ +# Deploy Minio on DC/OS [![Slack](https://slack.minio.io/slack?type=svg)](https://slack.minio.io) [![Go Report Card](https://goreportcard.com/badge/minio/minio)](https://goreportcard.com/report/minio/minio) [![Docker Pulls](https://img.shields.io/docker/pulls/minio/minio.svg?maxAge=604800)](https://hub.docker.com/r/minio/minio/) [![codecov](https://codecov.io/gh/minio/minio/branch/master/graph/badge.svg)](https://codecov.io/gh/minio/minio) + +To deploy Minio on DC/OS, you can use a Universe package, or create a customized config file. We at Minio recently released an [official universe package](https://github.com/mesosphere/universe/tree/version-3.x/repo/packages/M/minio/0) to enable single click Minio deployment on a DC/OS cluster. + +## 1. Prerequisites + +- DC/OS 1.8 or later running on your cluster. +- [Marathon-LB](https://dcos.io/docs/1.8/usage/service-discovery/marathon-lb/usage/) installed and running. +- IP address of the public agent(s) where Marathon-LB or an available hostname configured to point to the public agent(s) where Marathon-LB is running. + +## 2. Setting up Minio + +You can install Minio Universe package using the DC/OS GUI or CLI. + +### Minio installation on DC/OS GUI + +Visit the DC/OS admin page, and click on `Univers` on the left menu bar. Then click on the `Package` tab and search for Minio. Once you see the package, click the `Instal` button on the right hand side. Then, enter configuration values like the storage and service type you’d like to use with your Minio instance. Finally enter the public Marathon-LB IP address under `networking >> public-agent`, and click `Review and Install`. + +This completes the install process. Before you can access Minio server, get the access key and secret key from the Minio container logs. Click on `Services` and select Minio service in DC/OS admin page. Then go to the `logs` tab and copy the accesskey and secretkey. + +### Minio installation on DC/OS CLI + +To install Minio package via CLI, type + +```bash +$ dcos package install minio +``` + +## 3. Uninstalling Minio + +To uninstall Minio package via CLI, type + +```bash +$ dcos package uninstall minio +``` + +### Explore Further +- [Minio Erasure Code QuickStart Guide](https://docs.minio.io/docs/minio-erasure-code-quickstart-guide) +- [Use `mc` with Minio Server](https://docs.minio.io/docs/minio-client-quickstart-guide) +- [Use `aws-cli` with Minio Server](https://docs.minio.io/docs/aws-cli-with-minio) +- [Use `s3cmd` with Minio Server](https://docs.minio.io/docs/s3cmd-with-minio) +- [Use `minio-go` SDK with Minio Server](https://docs.minio.io/docs/golang-client-quickstart-guide) +- [The Minio documentation website](https://docs.minio.io) diff --git a/docs/orchestration/docker-swarm/README.md b/docs/orchestration/docker-swarm/README.md new file mode 100644 index 000000000..8d7ac385d --- /dev/null +++ b/docs/orchestration/docker-swarm/README.md @@ -0,0 +1,70 @@ +# Deploy Minio on Docker Swarm [![Slack](https://slack.minio.io/slack?type=svg)](https://slack.minio.io) [![Go Report Card](https://goreportcard.com/badge/minio/minio)](https://goreportcard.com/report/minio/minio) [![Docker Pulls](https://img.shields.io/docker/pulls/minio/minio.svg?maxAge=604800)](https://hub.docker.com/r/minio/minio/) [![codecov](https://codecov.io/gh/minio/minio/branch/master/graph/badge.svg)](https://codecov.io/gh/minio/minio) + +Docker Engine provides cluster management and orchestration features in Swarm mode. Minio server can be easily deployed in distributed mode on Swarm to create a multi-tenant, highly-available and scalable object store. + +As of [Docker Engine v1.13.0](https://blog.docker.com/2017/01/whats-new-in-docker-1-13/) (Docker Compose v3.0), Docker Swarm and Compose are [cross-compatible](https://docs.docker.com/compose/compose-file/#version-3). This allows a Compose file to be used as a template to deploy services on Swarm. We have used a Docker Compose file to create distributed Minio setup. + +## 1. Prerequisites + +* Familiarity with [Swarm mode key concepts](https://docs.docker.com/engine/swarm/key-concepts/). +* Docker engine v1.13.0 running on a cluster of [networked host machines](https://docs.docker.com/engine/swarm/swarm-tutorial/#/three-networked-host-machines). + +## 2. Create a Swarm + +SSH into the machine supposed to serve as Swarm manager. If the machine is named `manager`, you can SSH by + +```shell +docker-machine ssh manager +``` +After logging in to the designated manager node, create the Swarm by + +```shell +docker swarm init --advertise-addr +``` + +After the manager is up, [add worker nodes](https://docs.docker.com/engine/swarm/swarm-tutorial/add-nodes/) to the Swarm. Find detailed steps to create the Swarm on [Docker documentation site](https://docs.docker.com/engine/swarm/swarm-tutorial/create-swarm/). + +## 3. Deploy distributed Minio services + +Download the [Docker Compose file](./docker-compose.yaml) on your Swarm master. Then execute the command + +```shell +docker stack deploy --compose-file=docker-compose.yaml minio_stack +``` +This deploys services described in the Compose file as Docker stack `minio_stack`. Look up the `docker stack` [command reference](https://docs.docker.com/engine/reference/commandline/stack/) for more info. + +After the stack is successfully deployed, you should be able to access Minio server via [Minio Client](https://docs.minio.io/docs/minio-client-complete-guide) `mc` or your browser at http://[Node_Public_IP_Address]:[Expose_Port_on_Host] + +## 4. Remove distributed Minio services + +Remove the distributed Minio services and related network by + +```shell +docker stack rm minio_stack +``` + +### Notes + +* By default the Docker Compose file uses the Docker image for latest Minio server release. You can change the image tag to pull a specific [Minio Docker image](https://hub.docker.com/r/minio/minio/). + +* There are 4 minio distributed instances created by default. You can add more Minio services (upto total 16) to your Minio Swarm deployment. To add a deployment + * Replicate a service definition and change the name of the new service appropriately. + * Add a volume in volumes section, and update volume section in the service accordingly. + * Update the command section in each service. Specifically, add the drive location to be used as storage on the new service. + * Update the port number to exposed for the new service. + + Read more about distributed Minio [here](https://docs.minio.io/docs/distributed-minio-quickstart-guide). + +* By default the services use `local` volume driver. Refer to [Docker documentation](https://docs.docker.com/compose/compose-file/#/volume-configuration-reference) to explore further options. + +* Minio services in the Docker compose file expose ports 9001 to 9004. This allows multiple services to run on a host. Explore other configuration options in [Docker documentation](https://docs.docker.com/compose/compose-file/#/ports). + +* Docker Swarm uses ingress load balancing by default. You can configure [external load balancer based](https://docs.docker.com/engine/swarm/ingress/#/configure-an-external-load-balancer) on requirements. + +### Explore Further +- [Minio Erasure Code QuickStart Guide](https://docs.minio.io/docs/minio-erasure-code-quickstart-guide) +- [Use `mc` with Minio Server](https://docs.minio.io/docs/minio-client-quickstart-guide) +- [Use `aws-cli` with Minio Server](https://docs.minio.io/docs/aws-cli-with-minio) +- [Use `s3cmd` with Minio Server](https://docs.minio.io/docs/s3cmd-with-minio) +- [Use `minio-go` SDK with Minio Server](https://docs.minio.io/docs/golang-client-quickstart-guide) +- [The Minio documentation website](https://docs.minio.io) diff --git a/docs/orchestration/docker-swarm/docker-compose.yaml b/docs/orchestration/docker-swarm/docker-compose.yaml new file mode 100644 index 000000000..f6e482b72 --- /dev/null +++ b/docs/orchestration/docker-swarm/docker-compose.yaml @@ -0,0 +1,87 @@ +version: '3' + +services: + minio1: + image: minio/minio:RELEASE.2017-01-25T03-14-52Z + volumes: + - minio1-data:/export + ports: + - "9001:9000" + networks: + - minio_distributed + environment: + MINIO_ACCESS_KEY: AKIAIOSFODNN7EXAMPLE + MINIO_SECRET_KEY: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY + deploy: + restart_policy: + delay: 10s + max_attempts: 10 + window: 60s + command: server http://minio1/export http://minio2/export http://minio3/export http://minio4/export + + minio2: + image: minio/minio:RELEASE.2017-01-25T03-14-52Z + volumes: + - minio2-data:/export + ports: + - "9002:9000" + networks: + - minio_distributed + environment: + MINIO_ACCESS_KEY: AKIAIOSFODNN7EXAMPLE + MINIO_SECRET_KEY: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY + deploy: + restart_policy: + delay: 10s + max_attempts: 10 + window: 60s + command: server http://minio1/export http://minio2/export http://minio3/export http://minio4/export + + minio3: + image: minio/minio:RELEASE.2017-01-25T03-14-52Z + volumes: + - minio3-data:/export + ports: + - "9003:9000" + networks: + - minio_distributed + environment: + MINIO_ACCESS_KEY: AKIAIOSFODNN7EXAMPLE + MINIO_SECRET_KEY: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY + deploy: + restart_policy: + delay: 10s + max_attempts: 10 + window: 60s + command: server http://minio1/export http://minio2/export http://minio3/export http://minio4/export + + minio4: + image: minio/minio:RELEASE.2017-01-25T03-14-52Z + volumes: + - minio4-data:/export + ports: + - "9004:9000" + networks: + - minio_distributed + environment: + MINIO_ACCESS_KEY: AKIAIOSFODNN7EXAMPLE + MINIO_SECRET_KEY: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY + deploy: + restart_policy: + delay: 10s + max_attempts: 10 + window: 60s + command: server http://minio1/export http://minio2/export http://minio3/export http://minio4/export + +volumes: + minio1-data: + + minio2-data: + + minio3-data: + + minio4-data: + +networks: + minio_distributed: + driver: overlay diff --git a/docs/orchestration/kubernetes/README.md b/docs/orchestration/kubernetes/README.md new file mode 100644 index 000000000..526ade602 --- /dev/null +++ b/docs/orchestration/kubernetes/README.md @@ -0,0 +1,138 @@ +# Deploy Minio on Kubernetes [![Slack](https://slack.minio.io/slack?type=svg)](https://slack.minio.io) [![Go Report Card](https://goreportcard.com/badge/minio/minio)](https://goreportcard.com/report/minio/minio) [![Docker Pulls](https://img.shields.io/docker/pulls/minio/minio.svg?maxAge=604800)](https://hub.docker.com/r/minio/minio/) [![codecov](https://codecov.io/gh/minio/minio/branch/master/graph/badge.svg)](https://codecov.io/gh/minio/minio) + +Kubernetes constructs like Deployments and StatefulSets provide perfect platform to deploy Minio server in standalone, distributed or shared mode. In addition, using Minio [Helm](https://helm.sh) Chart, you can deploy Minio server with a single command on your cluster. + +Minio Helm Chart offers great deal of [customizability](#configuration), still if you'd rather like to deploy Minio using custom config files, you can do that as well. This [blog post](https://blog.minio.io/build-aws-s3-compatible-cloud-storage-on-gcp-with-minio-and-kubernetes-159cc99caea8#.8zesfh6tc) offers an introduction to running Minio on Kubernetes using .yaml configuration files. + +## 1. Prerequisites + +* Kubernetes 1.4+ with Beta APIs enabled for default standalone mode. +* Kubernetes 1.5+ with Beta APIs enabled to run Minio in [distributed mode](#distributed-minio). +* PV provisioner support in the underlying infrastructure. +* Helm package manager [installed](https://github.com/kubernetes/helm#install) on your Kubernetes cluster. + +## 2. Deploy Minio using Helm Chart + +Install Minio chart by + +```bash +$ helm install stable/minio +``` +Above command deploys Minio on the Kubernetes cluster in the default configuration. Below section lists all the configurable parameters of the Minio chart and their default values. + +### Configuration + +| Parameter | Description | Default | +|----------------------------|-------------------------------------|---------------------------------------------------------| +| `image` | Minio image name | `minio/minio` | +| `imageTag` | Minio image tag. Possible values listed [here](https://hub.docker.com/r/minio/minio/tags/).| `RELEASE.2017-01-25T03-14-52Z`| +| `imagePullPolicy` | Image pull policy | `Always` | +| `mode` | Minio server mode (`standalone`, `shared` or `distributed`)| `standalone` | +| `numberOfNodes` | Number of nodes (applicable only for Minio distributed mode). Should be 4 <= x <= 16 | `4` | +| `accessKey` | Default access key | `AKIAIOSFODNN7EXAMPLE` | +| `secretKey` | Default secret key | `wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY` | +| `configPath` | Default config file location | `~/.minio` | +| `mountPath` | Default mount location for persistent drive| `/export` | +| `serviceType` | Kubernetes service type | `LoadBalancer` | +| `servicePort` | Kubernetes port where service is exposed| `9000` | +| `persistence.enabled` | Use persistent volume to store data | `true` | +| `persistence.size` | Size of persistent volume claim | `10Gi` | +| `persistence.storageClass` | Type of persistent volume claim | `generic` | +| `persistence.accessMode` | ReadWriteOnce or ReadOnly | `ReadWriteOnce` | +| `resources` | CPU/Memory resource requests/limits | Memory: `256Mi`, CPU: `100m` | + +You can specify each parameter using the `--set key=value[,key=value]` argument to `helm install`. For example, + +```bash +$ helm install --name my-release \ + --set persistence.size=100Gi \ + stable/minio +``` + +The above command deploys Minio server with a 100Gi backing persistent volume. + +Alternately, you can provide a YAML file that specifies parameter values while installing the chart. For example, + +```bash +$ helm install --name my-release -f values.yaml stable/minio +``` + +### Distributed Minio + +This chart provisions a Minio server in standalone mode, by default. To provision Minio server in [distributed mode](https://docs.minio.io/docs/distributed-minio-quickstart-guide), set the `mode` field to `distributed`, + +```bash +$ helm install --set mode=distributed stable/minio +``` + +This provisions Minio server in distributed mode with 4 nodes. To change the number of nodes in your distributed Minio server, set the `numberOfNodes` field, + +```bash +$ helm install --set mode=distributed,numberOfNodes=8 stable/minio +``` + +This provisions Minio server in distributed mode with 8 nodes. Note that the `numberOfNodes` value should be an integer between 4 and 16 (inclusive). + +#### StatefulSet [limitations](http://kubernetes.io/docs/concepts/abstractions/controllers/statefulsets/#limitations) applicable to distributed Minio + +* StatefulSets need persistent storage, so the `persistence.enabled` flag is ignored when `mode` is set to `distributed`. +* When uninstalling a distributed Minio release, you'll need to manually delete volumes associated with the StatefulSet. + +### Shared Minio + +To provision Minio servers in [shared mode](https://github.com/minio/minio/blob/master/docs/shared-backend/README.md), set the `mode` field to `shared`, + +```bash +$ helm install --set mode=shared stable/minio +``` + +This provisions 4 Minio server nodes backed by single storage. To change the number of nodes in your shared Minio deployment, set the `numberOfNodes` field, + +```bash +$ helm install --set mode=shared,numberOfNodes=8 stable/minio +``` + +This provisions Minio server in shared mode with 8 nodes. + +### Persistence + +This chart provisions a PersistentVolumeClaim and mounts corresponding persistent volume to default location `/export`. You'll need physical storage available in the Kubernetes cluster for this to work. If you'd rather use `emptyDir`, disable PersistentVolumeClaim by: + +```bash +$ helm install --set persistence.enabled=false stable/minio +``` + +> *"An emptyDir volume is first created when a Pod is assigned to a Node, and exists as long as that Pod is running on that node. When a Pod is removed from a node for any reason, the data in the emptyDir is deleted forever."* + +## 3. Uninstalling the Chart + +Assuming your release is named as `my-release`, delete it using the command: + +```bash +$ helm delete my-release +``` + +The command removes all the Kubernetes components associated with the chart and deletes the release. + +### Notes + +* An instance of a chart running in a Kubernetes cluster is called a release. Helm automatically assigns a unique release name after installing the chart. You can also set your preferred name by: + +```bash +$ helm install --name my-release stable/minio +``` + +* To override the default keys, pass the access and secret keys as arguments to helm install. + +```bash +$ helm install --set accessKey=myaccesskey,secretKey=mysecretkey \ + stable/minio +``` + +### Explore Further +- [Minio Erasure Code QuickStart Guide](https://docs.minio.io/docs/minio-erasure-code-quickstart-guide) +- [Use `mc` with Minio Server](https://docs.minio.io/docs/minio-client-quickstart-guide) +- [Use `aws-cli` with Minio Server](https://docs.minio.io/docs/aws-cli-with-minio) +- [Use `s3cmd` with Minio Server](https://docs.minio.io/docs/s3cmd-with-minio) +- [Use `minio-go` SDK with Minio Server](https://docs.minio.io/docs/golang-client-quickstart-guide) +- [The Minio documentation website](https://docs.minio.io) diff --git a/docs/screenshots/Minio_Cloud_Native_Arch.png b/docs/screenshots/Minio_Cloud_Native_Arch.png new file mode 100644 index 000000000..d2946f49a Binary files /dev/null and b/docs/screenshots/Minio_Cloud_Native_Arch.png differ diff --git a/docs/screenshots/erasure-code.jpg b/docs/screenshots/erasure-code.jpg deleted file mode 100644 index 4963e26cc..000000000 Binary files a/docs/screenshots/erasure-code.jpg and /dev/null differ diff --git a/docs/screenshots/erasure-code.png b/docs/screenshots/erasure-code.png new file mode 100644 index 000000000..61d8bcbcc Binary files /dev/null and b/docs/screenshots/erasure-code.png differ diff --git a/docs/service/linux/README.md b/docs/service/linux/README.md index 84780b329..35772d3d1 100644 --- a/docs/service/linux/README.md +++ b/docs/service/linux/README.md @@ -28,7 +28,7 @@ EOT Download `minio.service` into `/etc/systemd/system/` ``` -( cd /etc/systemd/system/; curl -O https://raw.githubusercontent.com/minio/minio-systemd/master/minio.service ) +( cd /etc/systemd/system/; curl -O https://raw.githubusercontent.com/minio/minio-service/master/linux-systemd/minio.service ) ``` ## Enable Minio service diff --git a/docs/shared-backend/README.md b/docs/shared-backend/README.md index 26dc64f84..1dc565f27 100644 --- a/docs/shared-backend/README.md +++ b/docs/shared-backend/README.md @@ -1,76 +1,58 @@ # Shared Backend Minio Quickstart Guide [![Slack](https://slack.minio.io/slack?type=svg)](https://slack.minio.io) [![Go Report Card](https://goreportcard.com/badge/minio/minio)](https://goreportcard.com/report/minio/minio) [![Docker Pulls](https://img.shields.io/docker/pulls/minio/minio.svg?maxAge=604800)](https://hub.docker.com/r/minio/minio/) [![codecov](https://codecov.io/gh/minio/minio/branch/master/graph/badge.svg)](https://codecov.io/gh/minio/minio) -Minio now supports shared backend across multiple instances. This solves certain specific use cases. +Minio shared mode lets you use single [NAS](https://en.wikipedia.org/wiki/Network-attached_storage) (like NFS, GlusterFS, and other +distributed filesystems) as the storage backend for multiple Minio servers. Synchronization among Minio servers is taken care by design. +Read more about the Minio shared mode design [here](https://github.com/minio/minio/blob/master/docs/shared-backend/DESIGN.md). -## Use Cases +Minio shared mode is developed to solve several real world use cases, without any special configuration changes. Some of these are -- Minio on NAS -- Minio on Distributed Filesystems -- Multi-user Shared Backend. +- You have already invested in NAS and would like to use Minio to add S3 compatibility to your storage tier. +- You need to use NAS with an S3 interface due to your application architecture requirements. +- You expect huge traffic and need a load balanced S3 compatible server, serving files from a single NAS backend. -## Why Minio On Shared Backend? - -This feature allows Minio to serve a shared NAS drive across multiple Minio instances. There are no special configuration changes required to enable this feature. Access to files stored on NAS volume are locked and synchronized by default. +With a proxy running in front of multiple, shared mode Minio servers, it is very easy to create a Highly Available, load balanced, AWS S3 compatible storage system. # Get started -If you're aware of stand-alone Minio set up, the installation and running remains the same. +If you're aware of stand-alone Minio set up, the installation and running remains the same. ## 1. Prerequisites Install Minio - [Minio Quickstart Guide](https://docs.minio.io/docs/minio). -## 2. Run Minio On Shared Backend +## 2. Run Minio on Shared Backend -Below examples will clarify further for each operating system of your choice: +To run Minio shared backend instances, you need to start multiple Minio servers pointing to the same backend storage. We'll see examples on how to do this in the following sections. -### Ubuntu 16.04 LTS +*Note* -Run the following commands on all the object storage gateway servers where your NAS volume is accessible. By explicitly passing access and secret keys through the environment variable you make sure that all the gateway servers share the same key across. +- All the nodes running shared Minio need to have same access key and secret key. To achieve this, we export access key and secret key as environment variables on all the nodes before executing Minio server command. +- The drive paths below are for demonstration purposes only, you need to replace these with the actual drive paths/folders. -Example 1: Start Minio instance on a shared backend mounted and available at `/mnt/nfs`. +#### Minio shared mode on Ubuntu 16.04 LTS. + +You'll need the path to the shared volume, e.g. `/mnt/nfs`. Then run the following commands on all the nodes you'd like to launch Minio. -On linux server1 ```sh +export MINIO_ACCESS_KEY= +export MINIO_SECRET_KEY= minio server /mnt/nfs ``` -On linux server2 -```sh -minio server /mnt/nfs -``` +#### Minio shared mode on Windows 2012 Server -### Windows 2012 Server +You'll need the path to the shared volume, e.g. `\\remote-server\smb`. Then run the following commands on all the nodes you'd like to launch Minio. -Run the following commands on all the object storage gateway servers where your NAS volume is accessible. By explicitly passing access and secret keys through the environment variable you make sure that all the gateway servers share the same key across. - -Example 1: Start Minio instance on a shared backend mounted and available at `\\remote-server\smb`. - -On windows server1 ```cmd set MINIO_ACCESS_KEY=my-username set MINIO_SECRET_KEY=my-password minio.exe server \\remote-server\smb\export ``` -On windows server2 -```cmd -set MINIO_ACCESS_KEY=my-username -set MINIO_SECRET_KEY=my-password -minio.exe server \\remote-server\smb\export -``` +*Windows Tip* -Alternatively if `\\remote-server\smb` is mounted as `M:\` drive. +If a remote volume, e.g. `\\remote-server\smb` is mounted as a drive, e.g. `M:\`. You can use [`net use`](https://technet.microsoft.com/en-us/library/bb490717.aspx) command to map the drive to a folder. -On windows server1 -```cmd -set MINIO_ACCESS_KEY=my-username -set MINIO_SECRET_KEY=my-password -net use m: \\remote-server\smb\export /P:Yes -minio.exe server M:\export -``` - -On windows server2 ```cmd set MINIO_ACCESS_KEY=my-username set MINIO_SECRET_KEY=my-password @@ -80,7 +62,7 @@ minio.exe server M:\export ## 3. Test your setup -To test this setup, access the Minio server via browser or [`mc`](https://docs.minio.io/docs/minio-client-quickstart-guide). You’ll see the uploaded files are accessible from the node2 endpoint as well. +To test this setup, access the Minio server via browser or [`mc`](https://docs.minio.io/docs/minio-client-quickstart-guide). You’ll see the uploaded files are accessible from the all the Minio shared backend endpoints. ## Explore Further - [Use `mc` with Minio Server](https://docs.minio.io/docs/minio-client-quickstart-guide) @@ -88,5 +70,3 @@ To test this setup, access the Minio server via browser or [`mc`](https://docs.m - [Use `s3cmd` with Minio Server](https://docs.minio.io/docs/s3cmd-with-minio) - [Use `minio-go` SDK with Minio Server](https://docs.minio.io/docs/golang-client-quickstart-guide) - [The Minio documentation website](https://docs.minio.io) - - diff --git a/main.go b/main.go index 43d2a73ed..892305e51 100644 --- a/main.go +++ b/main.go @@ -65,5 +65,5 @@ func main() { console.Fatalln("Go runtime version check failed.", err) } - minio.Main(os.Args, os.Exit) + minio.Main(os.Args) } diff --git a/minio.spec b/minio.spec new file mode 100644 index 000000000..c409f9764 --- /dev/null +++ b/minio.spec @@ -0,0 +1,83 @@ +%define tag RELEASE.2017-02-16T01-47-30Z +%define subver %(echo %{tag} | sed -e 's/[^0-9]//g') +# git fetch https://github.com/minio/minio.git refs/tags/RELEASE.2017-02-16T01-47-30Z +# git rev-list -n 1 FETCH_HEAD +%define commitid 3d98311d9f4ceb78dba996dcdc0751253241e697 +Summary: Cloud Storage Server. +Name: minio +Version: 0.0.%{subver} +Release: 1 +Vendor: Minio, Inc. +License: Apache v2.0 +Group: Applications/File +Source0: https://github.com/minio/minio/archive/%{tag}.tar.gz +URL: https://www.minio.io/ +BuildRequires: golang >= 1.7 +BuildRoot: %{tmpdir}/%{name}-%{version}-root-%(id -u -n) + +## Disable debug packages. +%define debug_package %{nil} + +## Go related tags. +%define gobuild(o:) go build -ldflags "${LDFLAGS:-}" %{?**}; +%define gopath %{_libdir}/golang +%define import_path github.com/minio/minio + +%description +Minio is an object storage server released under Apache License v2.0. +It is compatible with Amazon S3 cloud storage service. It is best +suited for storing unstructured data such as photos, videos, log +files, backups and container / VM images. Size of an object can +range from a few KBs to a maximum of 5TB. + +%prep +%setup -qc +mv %{name}-*/* . + +install -d src/$(dirname %{import_path}) +ln -s ../../.. src/%{import_path} + +%build +export GOPATH=$(pwd) + +# setup flags like 'go run buildscripts/gen-ldflags.go' would do +tag=%{tag} +version=${tag#RELEASE.} +commitid=%{commitid} +scommitid=$(echo $commitid | cut -c1-12) +prefix=%{import_path}/cmd + +LDFLAGS=" +-X $prefix.Version=$version +-X $prefix.ReleaseTag=$tag +-X $prefix.CommitID=$commitid +-X $prefix.ShortCommitID=$scommitid +" + +%gobuild -o %{name} %{import_path} + +# check that version set properly +./%{name} version | tee v + +#Version: 2016-09-11T17-42-18Z +#Release-Tag: RELEASE.2016-09-11T17-42-18Z +#Commit-ID: 85e2d886bcb005d49f3876d6849a2b5a55e03cd3 +v=$(awk '/Version:/{print $2}' v) +test "$v" = $version +v=$(awk '/Release-Tag:/{print $2}' v) +test "$v" = $tag +v=$(awk '/Commit-ID:/{print $2}' v) +test "$v" = $commitid + +%install +rm -rf $RPM_BUILD_ROOT +install -d $RPM_BUILD_ROOT%{_sbindir} +install -p %{name} $RPM_BUILD_ROOT%{_sbindir} + +%clean +rm -rf $RPM_BUILD_ROOT + +%files +%defattr(644,root,root,755) +%doc README.md README_ZH.md +%attr(755,root,root) %{_sbindir}/minio diff --git a/pkg/disk/type_linux.go b/pkg/disk/type_linux.go index 98dceb66f..7de259a3c 100644 --- a/pkg/disk/type_linux.go +++ b/pkg/disk/type_linux.go @@ -40,7 +40,7 @@ var fsType2StringMap = map[string]string{ func getFSType(ftype int64) string { fsTypeHex := strconv.FormatInt(ftype, 16) fsTypeString, ok := fsType2StringMap[fsTypeHex] - if ok == false { + if !ok { return "UNKNOWN" } return fsTypeString diff --git a/pkg/madmin/API.md b/pkg/madmin/API.md index 506690783..7d5ba2a5e 100644 --- a/pkg/madmin/API.md +++ b/pkg/madmin/API.md @@ -36,13 +36,14 @@ func main() { ``` -| Service operations|LockInfo operations|Healing operations| -|:---|:---|:---| -|[`ServiceStatus`](#ServiceStatus)| [`ListLocks`](#ListLocks)| [`ListObjectsHeal`](#ListObjectsHeal)| -|[`ServiceRestart`](#ServiceRestart)| [`ClearLocks`](#ClearLocks)| [`ListBucketsHeal`](#ListBucketsHeal)| -| | |[`HealBucket`](#HealBucket) | -| | |[`HealObject`](#HealObject)| -| | |[`HealFormat`](#HealFormat)| +| Service operations|LockInfo operations|Healing operations|Config operations| Misc | +|:---|:---|:---|:---|:---| +|[`ServiceStatus`](#ServiceStatus)| [`ListLocks`](#ListLocks)| [`ListObjectsHeal`](#ListObjectsHeal)|[`GetConfig`](#GetConfig)| [`SetCredentials`](#SetCredentials)| +|[`ServiceRestart`](#ServiceRestart)| [`ClearLocks`](#ClearLocks)| [`ListBucketsHeal`](#ListBucketsHeal)|[`SetConfig`](#SetConfig)|| +| | |[`HealBucket`](#HealBucket) ||| +| | |[`HealObject`](#HealObject)||| +| | |[`HealFormat`](#HealFormat)||| +| | |[`ListUploadsHeal`](#ListUploadsHeal)||| ## 1. Constructor @@ -119,6 +120,9 @@ If successful restarts the running minio service, for distributed setup restarts log.Printf("Success") ``` + +## 3. Lock operations + ### ListLocks(bucket, prefix string, duration time.Duration) ([]VolumeLockInfo, error) If successful returns information on the list of locks held on ``bucket`` matching ``prefix`` for longer than ``duration`` seconds. @@ -149,6 +153,8 @@ __Example__ ``` +## 4. Heal operations + ### ListObjectsHeal(bucket, prefix string, recursive bool, doneCh <-chan struct{}) (<-chan ObjectInfo, error) If successful returns information on the list of objects that need healing in ``bucket`` matching ``prefix``. @@ -272,3 +278,122 @@ __Example__ log.Println("successfully healed storage format on available disks.") ``` + +## 5. Config operations + + +### GetConfig() ([]byte, error) +Get config.json of a minio setup. + +__Example__ + +``` go + configBytes, err := madmClnt.GetConfig() + if err != nil { + log.Fatalf("failed due to: %v", err) + } + + // Pretty-print config received as json. + var buf bytes.Buffer + err = json.Indent(buf, configBytes, "", "\t") + if err != nil { + log.Fatalf("failed due to: %v", err) + } + + log.Println("config received successfully: ", string(buf.Bytes())) +``` + +## 6. Misc operations + + + +### SetCredentials() error +Set new credentials of a Minio setup. + +__Example__ + +``` go + err = madmClnt.SetCredentials("YOUR-NEW-ACCESSKEY", "YOUR-NEW-SECRETKEY") + if err != nil { + log.Fatalln(err) + } + log.Println("New credentials successfully set.") + +``` + + +### SetConfig(config io.Reader) (SetConfigResult, error) +Set config.json of a minio setup and restart setup for configuration +change to take effect. + + +| Param | Type | Description | +|---|---|---| +|`st.Status` | _bool_ | true if set-config succeeded, false otherwise. | +|`st.NodeSummary.Name` | _string_ | Network address of the node. | +|`st.NodeSummary.ErrSet` | _bool_ | Bool representation indicating if an error is encountered with the node.| +|`st.NodeSummary.ErrMsg` | _string_ | String representation of the error (if any) on the node.| + + +__Example__ + +``` go + config := bytes.NewReader([]byte(`config.json contents go here`)) + result, err := madmClnt.SetConfig(config) + if err != nil { + log.Fatalf("failed due to: %v", err) + } + + var buf bytes.Buffer + enc := json.NewEncoder(&buf) + enc.SetEscapeHTML(false) + enc.SetIndent("", "\t") + err = enc.Encode(result) + if err != nil { + log.Fatalln(err) + } + log.Println("SetConfig: ", string(buf.Bytes())) +``` + + +### ListUploadsHeal(bucket, prefix string, recursive bool, doneCh <-chan struct{}) (<-chan UploadInfo, error) +List ongoing multipart uploads that need healing. + +| Param | Type | Description | +|---|---|---| +|`ui.Key` | _string_ | Name of the object being uploaded | +|`ui.UploadID` | _string_ | UploadID of the ongoing multipart upload | +|`ui.HealUploadInfo.Status` | _healStatus_| One of `Healthy`, `CanHeal`, `Corrupted`, `QuorumUnavailable`| +|`ui.Err`| _error_ | non-nil if fetching fetching healing information failed | + +__Example__ + +``` go + + // Set true if recursive listing is needed. + isRecursive := true + // List objects that need healing for a given bucket and + // prefix. + healUploadsCh, err := madmClnt.ListUploadsHeal(bucket, prefix, isRecursive, doneCh) + if err != nil { + log.Fatalln("Failed to get list of uploads to be healed: ", err) + } + + for upload := range healUploadsCh { + if upload.Err != nil { + log.Println("upload listing error: ", upload.Err) + } + + if upload.HealUploadInfo != nil { + switch healInfo := *upload.HealUploadInfo; healInfo.Status { + case madmin.CanHeal: + fmt.Println(upload.Key, " can be healed.") + case madmin.QuorumUnavailable: + fmt.Println(upload.Key, " can't be healed until quorum is available.") + case madmin.Corrupted: + fmt.Println(upload.Key, " can't be healed, not enough information.") + } + } + } + +``` diff --git a/pkg/madmin/api.go b/pkg/madmin/api.go index 2bf06b8b6..ef010583b 100644 --- a/pkg/madmin/api.go +++ b/pkg/madmin/api.go @@ -1,5 +1,5 @@ /* - * Minio Cloud Storage, (C) 2016 Minio, Inc. + * Minio Cloud Storage, (C) 2016, 2017 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -276,12 +276,7 @@ func (c AdminClient) dumpHTTP(req *http.Request, resp *http.Response) error { // Ends the http dump. _, err = fmt.Fprintln(c.traceOutput, "---------END-HTTP---------") - if err != nil { - return err - } - - // Returns success. - return nil + return err } // do - execute http request. diff --git a/pkg/madmin/config-commands.go b/pkg/madmin/config-commands.go new file mode 100644 index 000000000..e4f8afb53 --- /dev/null +++ b/pkg/madmin/config-commands.go @@ -0,0 +1,124 @@ +/* + * Minio Cloud Storage, (C) 2017 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package madmin + +import ( + "bytes" + "encoding/json" + "io" + "io/ioutil" + "net/http" + "net/url" +) + +const ( + configQueryParam = "config" +) + +// NodeSummary - represents the result of an operation part of +// set-config on a node. +type NodeSummary struct { + Name string `json:"name"` + ErrSet bool `json:"errSet"` + ErrMsg string `json:"errMsg"` +} + +// SetConfigResult - represents detailed results of a set-config +// operation. +type SetConfigResult struct { + NodeResults []NodeSummary `json:"nodeResults"` + Status bool `json:"status"` +} + +// GetConfig - returns the config.json of a minio setup. +func (adm *AdminClient) GetConfig() ([]byte, error) { + queryVal := make(url.Values) + queryVal.Set(configQueryParam, "") + + hdrs := make(http.Header) + hdrs.Set(minioAdminOpHeader, "get") + + reqData := requestData{ + queryValues: queryVal, + customHeaders: hdrs, + } + + // Execute GET on /?config to get config of a setup. + resp, err := adm.executeMethod("GET", reqData) + + defer closeResponse(resp) + if err != nil { + return nil, err + } + + if resp.StatusCode != http.StatusOK { + return nil, httpRespToErrorResponse(resp) + } + + // Return the JSON marshalled bytes to user. + return ioutil.ReadAll(resp.Body) +} + +// SetConfig - set config supplied as config.json for the setup. +func (adm *AdminClient) SetConfig(config io.Reader) (SetConfigResult, error) { + queryVal := url.Values{} + queryVal.Set(configQueryParam, "") + + // Set x-minio-operation to set. + hdrs := make(http.Header) + hdrs.Set(minioAdminOpHeader, "set") + + // Read config bytes to calculate MD5, SHA256 and content length. + configBytes, err := ioutil.ReadAll(config) + if err != nil { + return SetConfigResult{}, err + } + + reqData := requestData{ + queryValues: queryVal, + customHeaders: hdrs, + contentBody: bytes.NewReader(configBytes), + contentMD5Bytes: sumMD5(configBytes), + contentSHA256Bytes: sum256(configBytes), + } + + // Execute PUT on /?config to set config. + resp, err := adm.executeMethod("PUT", reqData) + + defer closeResponse(resp) + if err != nil { + return SetConfigResult{}, err + } + + if resp.StatusCode != http.StatusOK { + return SetConfigResult{}, httpRespToErrorResponse(resp) + } + + var result SetConfigResult + jsonBytes, err := ioutil.ReadAll(resp.Body) + if err != nil { + return SetConfigResult{}, err + } + + err = json.Unmarshal(jsonBytes, &result) + if err != nil { + return SetConfigResult{}, err + } + + return result, nil +} diff --git a/pkg/madmin/examples/get-config.go b/pkg/madmin/examples/get-config.go new file mode 100644 index 000000000..87e6790a0 --- /dev/null +++ b/pkg/madmin/examples/get-config.go @@ -0,0 +1,52 @@ +/* +build ignore + * Minio Cloud Storage, (C) 2017 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package main + +import ( + "bytes" + "encoding/json" + "log" + + "github.com/minio/minio/pkg/madmin" +) + +func main() { + // Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY are + // dummy values, please replace them with original values. + + // API requests are secure (HTTPS) if secure=true and insecure (HTTPS) otherwise. + // New returns an Minio Admin client object. + madmClnt, err := madmin.New("your-minio.example.com:9000", "YOUR-ACCESSKEYID", "YOUR-SECRETACCESSKEY", true) + if err != nil { + log.Fatalln(err) + } + + configBytes, err := madmClnt.GetConfig() + if err != nil { + log.Fatalf("failed due to: %v", err) + } + + // Pretty-print config received as json. + var buf bytes.Buffer + err = json.Indent(&buf, configBytes, "", "\t") + if err != nil { + log.Fatalf("failed due to: %v", err) + } + + log.Println("config received successfully: ", string(buf.Bytes())) +} diff --git a/pkg/madmin/examples/heal-bucket.go b/pkg/madmin/examples/heal-bucket.go index cb2559dcf..64767e6a6 100644 --- a/pkg/madmin/examples/heal-bucket.go +++ b/pkg/madmin/examples/heal-bucket.go @@ -29,9 +29,6 @@ func main() { // Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY are // dummy values, please replace them with original values. - // Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY are - // dummy values, please replace them with original values. - // API requests are secure (HTTPS) if secure=true and insecure (HTTPS) otherwise. // New returns an Minio Admin client object. madmClnt, err := madmin.New("your-minio.example.com:9000", "YOUR-ACCESSKEYID", "YOUR-SECRETACCESSKEY", true) diff --git a/pkg/madmin/examples/heal-uploads-list.go b/pkg/madmin/examples/heal-uploads-list.go new file mode 100644 index 000000000..08da11c02 --- /dev/null +++ b/pkg/madmin/examples/heal-uploads-list.go @@ -0,0 +1,74 @@ +// +build ignore + +package main + +/* + * Minio Cloud Storage, (C) 2017 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import ( + "fmt" + "log" + + "github.com/minio/minio/pkg/madmin" +) + +func main() { + + // Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY are + // dummy values, please replace them with original values. + + // API requests are secure (HTTPS) if secure=true and insecure (HTTPS) otherwise. + // New returns an Minio Admin client object. + madmClnt, err := madmin.New("your-minio.example.com:9000", "YOUR-ACCESSKEYID", "YOUR-SECRETACCESSKEY", true) + if err != nil { + log.Fatalln(err) + } + + bucket := "mybucket" + prefix := "myprefix" + + // Create a done channel to control 'ListUploadsHeal' go routine. + doneCh := make(chan struct{}) + // Indicate to our routine to exit cleanly upon return. + defer close(doneCh) + + // Set true if recursive listing is needed. + isRecursive := true + // List objects that need healing for a given bucket and + // prefix. + healUploadsCh, err := madmClnt.ListUploadsHeal(bucket, prefix, isRecursive, doneCh) + if err != nil { + log.Fatalln("Failed to get list of uploads to be healed: ", err) + } + + for upload := range healUploadsCh { + if upload.Err != nil { + log.Println("upload listing error: ", upload.Err) + } + + if upload.HealUploadInfo != nil { + switch healInfo := *upload.HealUploadInfo; healInfo.Status { + case madmin.CanHeal: + fmt.Println(upload.Key, " can be healed.") + case madmin.QuorumUnavailable: + fmt.Println(upload.Key, " can't be healed until quorum is available.") + case madmin.Corrupted: + fmt.Println(upload.Key, " can't be healed, not enough information.") + } + } + } +} diff --git a/pkg/madmin/examples/set-config.go b/pkg/madmin/examples/set-config.go new file mode 100644 index 000000000..6ab560121 --- /dev/null +++ b/pkg/madmin/examples/set-config.go @@ -0,0 +1,156 @@ +// +build ignore + +/* + * Minio Cloud Storage, (C) 2017 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "log" + + "github.com/minio/minio/pkg/madmin" +) + +var configJSON = []byte(`{ + "version": "13", + "credential": { + "accessKey": "minio", + "secretKey": "minio123" + }, + "region": "us-west-1", + "logger": { + "console": { + "enable": true, + "level": "fatal" + }, + "file": { + "enable": false, + "fileName": "", + "level": "" + } + }, + "notify": { + "amqp": { + "1": { + "enable": false, + "url": "", + "exchange": "", + "routingKey": "", + "exchangeType": "", + "mandatory": false, + "immediate": false, + "durable": false, + "internal": false, + "noWait": false, + "autoDeleted": false + } + }, + "nats": { + "1": { + "enable": false, + "address": "", + "subject": "", + "username": "", + "password": "", + "token": "", + "secure": false, + "pingInterval": 0, + "streaming": { + "enable": false, + "clusterID": "", + "clientID": "", + "async": false, + "maxPubAcksInflight": 0 + } + } + }, + "elasticsearch": { + "1": { + "enable": false, + "url": "", + "index": "" + } + }, + "redis": { + "1": { + "enable": false, + "address": "", + "password": "", + "key": "" + } + }, + "postgresql": { + "1": { + "enable": false, + "connectionString": "", + "table": "", + "host": "", + "port": "", + "user": "", + "password": "", + "database": "" + } + }, + "kafka": { + "1": { + "enable": false, + "brokers": null, + "topic": "" + } + }, + "webhook": { + "1": { + "enable": false, + "endpoint": "" + } + } + } +}`) + +func main() { + // Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY are + // dummy values, please replace them with original values. + + // Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY are + // dummy values, please replace them with original values. + + // API requests are secure (HTTPS) if secure=true and insecure (HTTPS) otherwise. + // New returns an Minio Admin client object. + madmClnt, err := madmin.New("your-minio.example.com:9000", "YOUR-ACCESSKEYID", "YOUR-SECRETACCESSKEY", true) + if err != nil { + log.Fatalln(err) + } + + result, err := madmClnt.SetConfig(bytes.NewReader(configJSON)) + if err != nil { + log.Fatalln(err) + } + + var buf bytes.Buffer + enc := json.NewEncoder(&buf) + enc.SetEscapeHTML(false) + enc.SetIndent("", "\t") + err = enc.Encode(result) + if err != nil { + log.Fatalln(err) + } + + fmt.Println("SetConfig: ", string(buf.Bytes())) +} diff --git a/pkg/madmin/examples/service-set-credentials.go b/pkg/madmin/examples/set-credentials.go similarity index 93% rename from pkg/madmin/examples/service-set-credentials.go rename to pkg/madmin/examples/set-credentials.go index 81f0dc203..35e81b958 100644 --- a/pkg/madmin/examples/service-set-credentials.go +++ b/pkg/madmin/examples/set-credentials.go @@ -36,7 +36,7 @@ func main() { log.Fatalln(err) } - err = madmClnt.ServiceSetCredentials("YOUR-NEW-ACCESSKEY", "YOUR-NEW-SECRETKEY") + err = madmClnt.SetCredentials("YOUR-NEW-ACCESSKEY", "YOUR-NEW-SECRETKEY") if err != nil { log.Fatalln(err) } diff --git a/pkg/madmin/generic-commands.go b/pkg/madmin/generic-commands.go new file mode 100644 index 000000000..384b74ce2 --- /dev/null +++ b/pkg/madmin/generic-commands.go @@ -0,0 +1,66 @@ +/* + * Minio Cloud Storage, (C) 2016, 2017 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package madmin + +import ( + "bytes" + "encoding/xml" + "net/http" + "net/url" +) + +// setCredsReq - xml to send to the server to set new credentials +type setCredsReq struct { + Username string `xml:"username"` + Password string `xml:"password"` +} + +// SetCredentials - Call Set Credentials API to set new access and secret keys in the specified Minio server +func (adm *AdminClient) SetCredentials(access, secret string) error { + + // Setup new request + reqData := requestData{} + reqData.queryValues = make(url.Values) + reqData.queryValues.Set("service", "") + reqData.customHeaders = make(http.Header) + reqData.customHeaders.Set(minioAdminOpHeader, "set-credentials") + + // Setup request's body + body, err := xml.Marshal(setCredsReq{Username: access, Password: secret}) + if err != nil { + return err + } + reqData.contentBody = bytes.NewReader(body) + reqData.contentLength = int64(len(body)) + reqData.contentMD5Bytes = sumMD5(body) + reqData.contentSHA256Bytes = sum256(body) + + // Execute GET on bucket to list objects. + resp, err := adm.executeMethod("POST", reqData) + + defer closeResponse(resp) + if err != nil { + return err + } + + // Return error to the caller if http response code is different from 200 + if resp.StatusCode != http.StatusOK { + return httpRespToErrorResponse(resp) + } + return nil +} diff --git a/pkg/madmin/heal-commands.go b/pkg/madmin/heal-commands.go index 7544a8f89..86042157c 100644 --- a/pkg/madmin/heal-commands.go +++ b/pkg/madmin/heal-commands.go @@ -3,6 +3,8 @@ * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. + + * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 @@ -25,6 +27,10 @@ import ( "time" ) +const ( + maxUploadsList = 1000 +) + // listBucketHealResult container for listObjects response. type listBucketHealResult struct { // A response can contain CommonPrefixes only if you have @@ -156,16 +162,75 @@ type ObjectInfo struct { HealObjectInfo *HealObjectInfo `json:"healObjectInfo,omitempty"` } +// UploadInfo - represents an ongoing upload that needs to be healed. +type UploadInfo struct { + Key string `json:"name"` // Name of the object being uploaded. + + UploadID string `json:"uploadId"` // UploadID + // Owner name. + Owner struct { + DisplayName string `json:"name"` + ID string `json:"id"` + } `json:"owner"` + + // The class of storage used to store the object. + StorageClass string `json:"storageClass"` + + Initiated time.Time `json:"initiated"` // Time at which upload was initiated. + + // Error + Err error `json:"-"` + HealUploadInfo *HealObjectInfo `json:"healObjectInfo,omitempty"` +} + +// Initiator - has same properties as Owner. +type Initiator Owner + +// upload - represents an ongoing multipart upload. +type upload struct { + Key string + UploadID string `xml:"UploadId"` + Initiator Initiator + Owner Owner + StorageClass string + Initiated time.Time + HealUploadInfo *HealObjectInfo `xml:"HealObjectInfo,omitempty"` +} + +// listUploadsHealResponse - represents ListUploadsHeal response. +type listUploadsHealResponse struct { + XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ ListMultipartUploadsResult" json:"-"` + + Bucket string + KeyMarker string + UploadIDMarker string `xml:"UploadIdMarker"` + NextKeyMarker string + NextUploadIDMarker string `xml:"NextUploadIdMarker"` + Delimiter string + Prefix string + EncodingType string `xml:"EncodingType,omitempty"` + MaxUploads int + IsTruncated bool + + // List of pending uploads. + Uploads []upload `xml:"Upload"` + + // Delimed common prefixes. + CommonPrefixes []commonPrefix +} + type healQueryKey string const ( - healBucket healQueryKey = "bucket" - healObject healQueryKey = "object" - healPrefix healQueryKey = "prefix" - healMarker healQueryKey = "marker" - healDelimiter healQueryKey = "delimiter" - healMaxKey healQueryKey = "max-key" - healDryRun healQueryKey = "dry-run" + healBucket healQueryKey = "bucket" + healObject healQueryKey = "object" + healPrefix healQueryKey = "prefix" + healMarker healQueryKey = "marker" + healDelimiter healQueryKey = "delimiter" + healMaxKey healQueryKey = "max-key" + healDryRun healQueryKey = "dry-run" + healUploadIDMarker healQueryKey = "upload-id-marker" + healMaxUpload healQueryKey = "max-uploads" ) // mkHealQueryVal - helper function to construct heal REST API query params. @@ -211,10 +276,7 @@ func (adm *AdminClient) listObjectsHeal(bucket, prefix, marker, delimiter string } err = xml.NewDecoder(resp.Body).Decode(&toBeHealedObjects) - if err != nil { - return toBeHealedObjects, err - } - return toBeHealedObjects, nil + return toBeHealedObjects, err } // ListObjectsHeal - Lists upto maxKeys objects that needing heal matching bucket, prefix, marker, delimiter. @@ -435,3 +497,139 @@ func (adm *AdminClient) HealFormat(dryrun bool) error { return nil } + +// mkUploadsHealQuery - helper function to construct query params for +// ListUploadsHeal API. +func mkUploadsHealQuery(bucket, prefix, marker, uploadIDMarker, delimiter, maxUploadsStr string) url.Values { + + queryVal := make(url.Values) + queryVal.Set("heal", "") + queryVal.Set(string(healBucket), bucket) + queryVal.Set(string(healPrefix), prefix) + queryVal.Set(string(healMarker), marker) + queryVal.Set(string(healUploadIDMarker), uploadIDMarker) + queryVal.Set(string(healDelimiter), delimiter) + queryVal.Set(string(healMaxUpload), maxUploadsStr) + return queryVal +} + +func (adm *AdminClient) listUploadsHeal(bucket, prefix, marker, uploadIDMarker, delimiter string, maxUploads int) (listUploadsHealResponse, error) { + // Construct query params. + maxUploadsStr := fmt.Sprintf("%d", maxUploads) + queryVal := mkUploadsHealQuery(bucket, prefix, marker, uploadIDMarker, delimiter, maxUploadsStr) + + hdrs := make(http.Header) + hdrs.Set(minioAdminOpHeader, "list-uploads") + + reqData := requestData{ + queryValues: queryVal, + customHeaders: hdrs, + } + + // Empty 'list' of objects to be healed. + toBeHealedUploads := listUploadsHealResponse{} + + // Execute GET on /?heal to list objects needing heal. + resp, err := adm.executeMethod("GET", reqData) + + defer closeResponse(resp) + if err != nil { + return listUploadsHealResponse{}, err + } + + if resp.StatusCode != http.StatusOK { + return toBeHealedUploads, httpRespToErrorResponse(resp) + + } + + err = xml.NewDecoder(resp.Body).Decode(&toBeHealedUploads) + if err != nil { + return listUploadsHealResponse{}, err + } + + return toBeHealedUploads, nil +} + +// ListUploadsHeal - issues list heal uploads API request +func (adm *AdminClient) ListUploadsHeal(bucket, prefix string, recursive bool, + doneCh <-chan struct{}) (<-chan UploadInfo, error) { + + // Default listing is delimited at "/" + delimiter := "/" + if recursive { + // If recursive we do not delimit. + delimiter = "" + } + + uploadIDMarker := "" + + // Allocate new list objects channel. + uploadStatCh := make(chan UploadInfo, maxUploadsList) + + // Initiate list objects goroutine here. + go func(uploadStatCh chan<- UploadInfo) { + defer close(uploadStatCh) + // Save marker for next request. + var marker string + for { + // Get list of objects a maximum of 1000 per request. + result, err := adm.listUploadsHeal(bucket, prefix, marker, + uploadIDMarker, delimiter, maxUploadsList) + if err != nil { + uploadStatCh <- UploadInfo{ + Err: err, + } + return + } + + // If contents are available loop through and + // send over channel. + for _, upload := range result.Uploads { + select { + // Send upload info. + case uploadStatCh <- UploadInfo{ + Key: upload.Key, + Initiated: upload.Initiated, + HealUploadInfo: upload.HealUploadInfo, + }: + // If receives done from the caller, return here. + case <-doneCh: + return + } + } + + // Send all common prefixes if any. NOTE: + // prefixes are only present if the request is + // delimited. + for _, prefix := range result.CommonPrefixes { + upload := UploadInfo{} + upload.Key = prefix.Prefix + select { + // Send object prefixes. + case uploadStatCh <- upload: + // If receives done from the caller, return here. + case <-doneCh: + return + } + } + + // If next uploadID marker is present, set it + // for the next request. + if result.NextUploadIDMarker != "" { + uploadIDMarker = result.NextUploadIDMarker + } + + // If next marker present, save it for next request. + if result.KeyMarker != "" { + marker = result.KeyMarker + } + + // Listing ends result is not truncated, + // return right here. + if !result.IsTruncated { + return + } + } + }(uploadStatCh) + return uploadStatCh, nil +} diff --git a/pkg/madmin/service-commands.go b/pkg/madmin/service-commands.go index ee22a3feb..62bf8cb42 100644 --- a/pkg/madmin/service-commands.go +++ b/pkg/madmin/service-commands.go @@ -18,10 +18,7 @@ package madmin import ( - "bytes" "encoding/json" - "encoding/xml" - "errors" "io/ioutil" "net/http" "net/url" @@ -94,49 +91,3 @@ func (adm *AdminClient) ServiceRestart() error { } return nil } - -// setCredsReq - xml to send to the server to set new credentials -type setCredsReq struct { - Username string `xml:"username"` - Password string `xml:"password"` -} - -// ServiceSetCredentials - Call Service Set Credentials API to set new access and secret keys in the specified Minio server -func (adm *AdminClient) ServiceSetCredentials(access, secret string) error { - - // Disallow sending with the server if the connection is not secure - if !adm.secure { - return errors.New("setting new credentials requires HTTPS connection to the server") - } - - // Setup new request - reqData := requestData{} - reqData.queryValues = make(url.Values) - reqData.queryValues.Set("service", "") - reqData.customHeaders = make(http.Header) - reqData.customHeaders.Set(minioAdminOpHeader, "set-credentials") - - // Setup request's body - body, err := xml.Marshal(setCredsReq{Username: access, Password: secret}) - if err != nil { - return err - } - reqData.contentBody = bytes.NewReader(body) - reqData.contentLength = int64(len(body)) - reqData.contentMD5Bytes = sumMD5(body) - reqData.contentSHA256Bytes = sum256(body) - - // Execute GET on bucket to list objects. - resp, err := adm.executeMethod("POST", reqData) - - defer closeResponse(resp) - if err != nil { - return err - } - - // Return error to the caller if http response code is different from 200 - if resp.StatusCode != http.StatusOK { - return httpRespToErrorResponse(resp) - } - return nil -} diff --git a/pkg/mimedb/db_test.go b/pkg/mimedb/db_test.go index 7ef73b65c..957e558db 100644 --- a/pkg/mimedb/db_test.go +++ b/pkg/mimedb/db_test.go @@ -1,5 +1,5 @@ /* - * mime-db: Mime Database, (C) 2015, 2016 Minio, Inc. + * mime-db: Mime Database, (C) 2015, 2016, 2017 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,7 +25,7 @@ func TestMimeLookup(t *testing.T) { t.Fatalf("Invalid content type are found expected \"application/x-msdownload\", got %s", contentType) } compressible := DB["txt"].Compressible - if compressible != false { + if compressible { t.Fatalf("Invalid content type are found expected \"false\", got %t", compressible) } } diff --git a/pkg/objcache/README.md b/pkg/objcache/README.md index 283ceff6f..fdafea98d 100644 --- a/pkg/objcache/README.md +++ b/pkg/objcache/README.md @@ -6,22 +6,24 @@ package objcache Package objcache implements in memory caching methods. +CONSTANTS + + const ( + // NoExpiry represents caches to be permanent and can only be deleted. + NoExpiry = time.Duration(0) + + // DefaultExpiry represents three days time duration when individual entries will be expired. + DefaultExpiry = time.Duration(3 * 24 * time.Hour) + ) + VARIABLES -var DefaultExpiry = time.Duration(72 * time.Hour) // 72hrs. - - DefaultExpiry represents default time duration value when individual - entries will be expired. - var ErrCacheFull = errors.New("Not enough space in cache") ErrCacheFull - cache is full. var ErrKeyNotFoundInCache = errors.New("Key not found in cache") ErrKeyNotFoundInCache - key not found in cache. -var NoExpiry = time.Duration(0) - NoExpiry represents caches to be permanent and can only be deleted. - TYPES type Cache struct { diff --git a/pkg/objcache/objcache.go b/pkg/objcache/objcache.go index d07a86214..43eb31dc2 100644 --- a/pkg/objcache/objcache.go +++ b/pkg/objcache/objcache.go @@ -1,5 +1,5 @@ /* - * Minio Cloud Storage, (C) 2016 Minio, Inc. + * Minio Cloud Storage, (C) 2016, 2017 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,18 +27,20 @@ import ( "time" ) -// NoExpiry represents caches to be permanent and can only be deleted. -var NoExpiry = time.Duration(0) +const ( + // NoExpiry represents caches to be permanent and can only be deleted. + NoExpiry = time.Duration(0) -// DefaultExpiry represents default time duration value when individual entries will be expired. -var DefaultExpiry = time.Duration(72 * time.Hour) // 72hrs. + // DefaultExpiry represents three days time duration when individual entries will be expired. + DefaultExpiry = time.Duration(3 * 24 * time.Hour) -// DefaultBufferRatio represents default ratio used to calculate the -// individual cache entry buffer size. -var DefaultBufferRatio = uint64(10) + // defaultBufferRatio represents default ratio used to calculate the + // individual cache entry buffer size. + defaultBufferRatio = uint64(10) -// DefaultGCPercent represents default garbage collection target percentage. -var DefaultGCPercent = 20 + // defaultGCPercent represents default garbage collection target percentage. + defaultGCPercent = 20 +) // buffer represents the in memory cache of a single entry. // buffer carries value of the data and last accessed time. @@ -87,9 +89,10 @@ type Cache struct { // duration. If the expiry duration is less than one // (or NoExpiry), the items in the cache never expire // (by default), and must be deleted manually. -func New(maxSize uint64, expiry time.Duration) *Cache { +func New(maxSize uint64, expiry time.Duration) (c *Cache, err error) { if maxSize == 0 { - panic("objcache: setting maximum cache size to zero is forbidden.") + err = errors.New("invalid maximum cache size") + return c, err } // A garbage collection is triggered when the ratio @@ -106,20 +109,20 @@ func New(maxSize uint64, expiry time.Duration) *Cache { // when we get to 8M. // // Set this value to 20% if caching is enabled. - debug.SetGCPercent(DefaultGCPercent) + debug.SetGCPercent(defaultGCPercent) // Max cache entry size - indicates the // maximum buffer per key that can be held in // memory. Currently this value is 1/10th // the size of requested cache size. maxCacheEntrySize := func() uint64 { - i := maxSize / DefaultBufferRatio + i := maxSize / defaultBufferRatio if i == 0 { i = maxSize } return i }() - c := &Cache{ + c = &Cache{ onceGC: sync.Once{}, maxSize: maxSize, maxCacheEntrySize: maxCacheEntrySize, @@ -134,7 +137,7 @@ func New(maxSize uint64, expiry time.Duration) *Cache { // Start garbage collection routine to expire objects. c.StartGC() } - return c + return c, nil } // ErrKeyNotFoundInCache - key not found in cache. @@ -177,7 +180,7 @@ func (c *Cache) Create(key string, size int64) (w io.WriteCloser, err error) { // Change GC percent if the current cache usage // is already 75% of the maximum allowed usage. if c.currentSize > (75 * c.maxSize / 100) { - c.onceGC.Do(func() { debug.SetGCPercent(DefaultGCPercent - 10) }) + c.onceGC.Do(func() { debug.SetGCPercent(defaultGCPercent - 10) }) } c.mutex.Unlock() diff --git a/pkg/objcache/objcache_test.go b/pkg/objcache/objcache_test.go index 25cd1c69a..147c2fcc4 100644 --- a/pkg/objcache/objcache_test.go +++ b/pkg/objcache/objcache_test.go @@ -43,7 +43,11 @@ func TestObjExpiry(t *testing.T) { // Test case 1 validates running of GC. testCase := testCases[0] - cache := New(testCase.cacheSize, testCase.expiry) + cache, err := New(testCase.cacheSize, testCase.expiry) + if err != nil { + t.Fatalf("Unable to create new objcache") + } + cache.OnEviction = func(key string) {} w, err := cache.Create("test", 1) if err != nil { @@ -126,15 +130,23 @@ func TestObjCache(t *testing.T) { // Test 1 validating Open failure. testCase := testCases[0] - cache := New(testCase.cacheSize, testCase.expiry) - _, err := cache.Open("test", fakeObjModTime) + cache, err := New(testCase.cacheSize, testCase.expiry) + if err != nil { + t.Fatalf("Unable to create new objcache") + } + + _, err = cache.Open("test", fakeObjModTime) if testCase.err != err { t.Errorf("Test case 2 expected to pass, failed instead %s", err) } // Test 2 validating Create failure. testCase = testCases[1] - cache = New(testCase.cacheSize, testCase.expiry) + cache, err = New(testCase.cacheSize, testCase.expiry) + if err != nil { + t.Fatalf("Unable to create new objcache") + } + _, err = cache.Create("test", 2) if testCase.err != err { t.Errorf("Test case 2 expected to pass, failed instead %s", err) @@ -144,7 +156,11 @@ func TestObjCache(t *testing.T) { // Subsequently we Close() without writing any data, to receive // `io.ErrShortBuffer` testCase = testCases[2] - cache = New(testCase.cacheSize, testCase.expiry) + cache, err = New(testCase.cacheSize, testCase.expiry) + if err != nil { + t.Fatalf("Unable to create new objcache") + } + w, err := cache.Create("test", 1) if testCase.err != err { t.Errorf("Test case 3 expected to pass, failed instead %s", err) @@ -156,7 +172,11 @@ func TestObjCache(t *testing.T) { // Test 4 validates Create and Close succeeds successfully caching // the writes. testCase = testCases[3] - cache = New(testCase.cacheSize, testCase.expiry) + cache, err = New(testCase.cacheSize, testCase.expiry) + if err != nil { + t.Fatalf("Unable to create new objcache") + } + w, err = cache.Create("test", 5) if testCase.err != err { t.Errorf("Test case 4 expected to pass, failed instead %s", err) @@ -184,7 +204,11 @@ func TestObjCache(t *testing.T) { // Test 5 validates Delete succeeds and Open fails with err testCase = testCases[4] - cache = New(testCase.cacheSize, testCase.expiry) + cache, err = New(testCase.cacheSize, testCase.expiry) + if err != nil { + t.Fatalf("Unable to create new objcache") + } + w, err = cache.Create("test", 5) if err != nil { t.Errorf("Test case 5 expected to pass, failed instead %s", err) @@ -204,7 +228,11 @@ func TestObjCache(t *testing.T) { // Test 6 validates OnEviction being called upon Delete is being invoked. testCase = testCases[5] - cache = New(testCase.cacheSize, testCase.expiry) + cache, err = New(testCase.cacheSize, testCase.expiry) + if err != nil { + t.Fatalf("Unable to create new objcache") + } + w, err = cache.Create("test", 5) if err != nil { t.Errorf("Test case 6 expected to pass, failed instead %s", err) @@ -227,7 +255,11 @@ func TestObjCache(t *testing.T) { // Test 7 validates rejecting requests when excess data is being saved. testCase = testCases[6] - cache = New(testCase.cacheSize, testCase.expiry) + cache, err = New(testCase.cacheSize, testCase.expiry) + if err != nil { + t.Fatalf("Unable to create new objcache") + } + w, err = cache.Create("test1", 5) if err != nil { t.Errorf("Test case 7 expected to pass, failed instead %s", err) @@ -245,7 +277,11 @@ func TestObjCache(t *testing.T) { // Test 8 validates rejecting Writes which write excess data. testCase = testCases[7] - cache = New(testCase.cacheSize, testCase.expiry) + cache, err = New(testCase.cacheSize, testCase.expiry) + if err != nil { + t.Fatalf("Unable to create new objcache") + } + w, err = cache.Create("test1", 5) if err != nil { t.Errorf("Test case 8 expected to pass, failed instead %s", err) @@ -267,7 +303,11 @@ func TestObjCache(t *testing.T) { // TestStateEntryPurge - tests if objCache purges stale entry and returns ErrKeyNotFoundInCache. func TestStaleEntryPurge(t *testing.T) { - cache := New(1024, NoExpiry) + cache, err := New(1024, NoExpiry) + if err != nil { + t.Fatalf("Unable to create new objcache") + } + w, err := cache.Create("test", 5) if err != nil { t.Errorf("Test case expected to pass, failed instead %s", err) diff --git a/pkg/quick/encoding.go b/pkg/quick/encoding.go new file mode 100644 index 000000000..2d2060a55 --- /dev/null +++ b/pkg/quick/encoding.go @@ -0,0 +1,137 @@ +/* + * Quick - Quick key value store for config files and persistent state files + * + * Quick (C) 2017 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package quick + +import ( + "bytes" + "encoding/json" + "io/ioutil" + "os" + "path/filepath" + "runtime" + "strings" + + yaml "gopkg.in/yaml.v2" +) + +// ConfigEncoding is a generic interface which +// marshal/unmarshal configuration. +type ConfigEncoding interface { + Unmarshal([]byte, interface{}) error + Marshal(interface{}) ([]byte, error) +} + +// YAML encoding implements ConfigEncoding +type yamlEncoding struct{} + +func (y yamlEncoding) Unmarshal(b []byte, v interface{}) error { + return yaml.Unmarshal(b, v) +} + +func (y yamlEncoding) Marshal(v interface{}) ([]byte, error) { + return yaml.Marshal(v) +} + +// JSON encoding implements ConfigEncoding +type jsonEncoding struct{} + +func (j jsonEncoding) Unmarshal(b []byte, v interface{}) error { + err := json.Unmarshal(b, v) + if err != nil { + // Try to return a sophisticated json error message if possible + switch err := err.(type) { + case *json.SyntaxError: + return FormatJSONSyntaxError(bytes.NewReader(b), err) + default: + return err + } + } + return nil +} + +func (j jsonEncoding) Marshal(v interface{}) ([]byte, error) { + return json.MarshalIndent(v, "", "\t") +} + +// Convert a file extension to the appropriate struct capable +// to marshal/unmarshal data +func ext2EncFormat(fileExtension string) ConfigEncoding { + // Lower the file extension + ext := strings.ToLower(fileExtension) + ext = strings.TrimPrefix(ext, ".") + // Return the appropriate encoder/decoder according + // to the extension + switch ext { + case "yml", "yaml": + // YAML + return yamlEncoding{} + default: + // JSON + return jsonEncoding{} + } +} + +// toMarshaller returns the right marshal function according +// to the given file extension +func toMarshaller(ext string) func(interface{}) ([]byte, error) { + return ext2EncFormat(ext).Marshal +} + +// toUnmarshaller returns the right marshal function according +// to the given file extension +func toUnmarshaller(ext string) func([]byte, interface{}) error { + return ext2EncFormat(ext).Unmarshal +} + +// saveFileConfig marshals with the right encoding format +// according to the filename extension, if no extension is +// provided, json will be selected. +func saveFileConfig(filename string, v interface{}) error { + // Fetch filename's extension + ext := filepath.Ext(filename) + // Marshal data + dataBytes, err := toMarshaller(ext)(v) + if err != nil { + return err + } + if runtime.GOOS == "windows" { + dataBytes = []byte(strings.Replace(string(dataBytes), "\n", "\r\n", -1)) + } + // Save data. + return writeFile(filename, dataBytes) + +} + +// loadFileConfig unmarshals the file's content with the right +// decoder format according to the filename extension. If no +// extension is provided, json will be selected by default. +func loadFileConfig(filename string, v interface{}) error { + if _, err := os.Stat(filename); err != nil { + return err + } + fileData, err := ioutil.ReadFile(filename) + if err != nil { + return err + } + if runtime.GOOS == "windows" { + fileData = []byte(strings.Replace(string(fileData), "\r\n", "\n", -1)) + } + // Unmarshal file's content + return toUnmarshaller(filepath.Ext(filename))(fileData, v) +} diff --git a/pkg/quick/errorutil.go b/pkg/quick/errorutil.go index b6bb92edf..063af3d5d 100644 --- a/pkg/quick/errorutil.go +++ b/pkg/quick/errorutil.go @@ -1,7 +1,7 @@ /* * Quick - Quick key value store for config files and persistent state files * - * Minio Client (C) 2015 Minio, Inc. + * Quick (C) 2015 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/pkg/quick/quick.go b/pkg/quick/quick.go index 15094e522..26cae6983 100644 --- a/pkg/quick/quick.go +++ b/pkg/quick/quick.go @@ -1,7 +1,7 @@ /* * Quick - Quick key value store for config files and persistent state files * - * Minio Client (C) 2015 Minio, Inc. + * Quick (C) 2015 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,14 +19,11 @@ package quick import ( - "bytes" "encoding/json" "fmt" "io/ioutil" "os" "reflect" - "runtime" - "strings" "sync" "github.com/fatih/structs" @@ -85,34 +82,12 @@ func New(data interface{}) (Config, error) { // CheckVersion - loads json and compares the version number provided returns back true or false - any failure // is returned as error. func CheckVersion(filename string, version string) (bool, error) { - _, err := os.Stat(filename) - if err != nil { - return false, err - } - - fileData, err := ioutil.ReadFile(filename) - if err != nil { - return false, err - } - - if runtime.GOOS == "windows" { - fileData = []byte(strings.Replace(string(fileData), "\r\n", "\n", -1)) - } data := struct { Version string }{ Version: "", } - err = json.Unmarshal(fileData, &data) - if err != nil { - switch err := err.(type) { - case *json.SyntaxError: - return false, FormatJSONSyntaxError(bytes.NewReader(fileData), err) - default: - return false, err - } - } - config, err := New(data) + config, err := Load(filename, &data) if err != nil { return false, err } @@ -124,35 +99,14 @@ func CheckVersion(filename string, version string) (bool, error) { // Load - loads json config from filename for the a given struct data func Load(filename string, data interface{}) (Config, error) { - _, err := os.Stat(filename) - if err != nil { - return nil, err - } - - fileData, err := ioutil.ReadFile(filename) - if err != nil { - return nil, err - } - - if runtime.GOOS == "windows" { - fileData = []byte(strings.Replace(string(fileData), "\r\n", "\n", -1)) - } - - err = json.Unmarshal(fileData, &data) - if err != nil { - switch err := err.(type) { - case *json.SyntaxError: - return nil, FormatJSONSyntaxError(bytes.NewReader(fileData), err) - default: - return nil, err - } - } - config, err := New(data) if err != nil { return nil, err } - + err = config.Load(filename) + if err != nil { + return nil, err + } return config, nil } @@ -194,7 +148,9 @@ func (d config) String() string { return string(configBytes) } -// Save writes config data in JSON format to a file. +// Save writes config data to a file. Data format +// is selected based on file extension or JSON if +// not provided. func (d config) Save(filename string) error { d.lock.Lock() defer d.lock.Unlock() @@ -222,53 +178,26 @@ func (d config) Save(filename string) error { return err } } - // Proceed to create or overwrite file. - jsonData, err := json.MarshalIndent(d.data, "", "\t") - if err != nil { - return err - } - - if runtime.GOOS == "windows" { - jsonData = []byte(strings.Replace(string(jsonData), "\n", "\r\n", -1)) - } // Save data. - return writeFile(filename, jsonData) + return saveFileConfig(filename, d.data) } -// Load - loads JSON config from file and merge with currently set values +// Load - loads config from file and merge with currently set values +// File content format is guessed from the file name extension, if not +// available, consider that we have JSON. func (d *config) Load(filename string) error { d.lock.Lock() defer d.lock.Unlock() - _, err := os.Stat(filename) - if err != nil { - return err - } - - fileData, err := ioutil.ReadFile(filename) - if err != nil { - return err - } - - if runtime.GOOS == "windows" { - fileData = []byte(strings.Replace(string(fileData), "\r\n", "\n", -1)) - } - st := structs.New(d.data) f, ok := st.FieldOk("Version") if !ok { return fmt.Errorf("Argument struct [%s] does not contain field \"Version\"", st.Name()) } - err = json.Unmarshal(fileData, d.data) - if err != nil { - switch err := err.(type) { - case *json.SyntaxError: - return FormatJSONSyntaxError(bytes.NewReader(fileData), err) - default: - return err - } + if err := loadFileConfig(filename, d.data); err != nil { + return err } if err := CheckData(d.data); err != nil { diff --git a/pkg/quick/quick_test.go b/pkg/quick/quick_test.go index d14a505e7..561255af3 100644 --- a/pkg/quick/quick_test.go +++ b/pkg/quick/quick_test.go @@ -1,7 +1,7 @@ /* * Quick - Quick key value store for config files and persistent state files * - * Minio Client (C) 2015 Minio, Inc. + * Quick (C) 2015 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,11 @@ package quick_test import ( "encoding/json" + "io/ioutil" "os" + "reflect" + "runtime" + "strings" "testing" "github.com/minio/minio/pkg/quick" @@ -122,6 +126,106 @@ func (s *MySuite) TestLoadFile(c *C) { c.Assert(saveMe2, DeepEquals, saveMe1) } +func (s *MySuite) TestYAMLFormat(c *C) { + testYAML := "test.yaml" + defer os.RemoveAll(testYAML) + + type myStruct struct { + Version string + User string + Password string + Directories []string + } + + plainYAML := `version: "1" +user: guest +password: nopassword +directories: +- Work +- Documents +- Music +` + + if runtime.GOOS == "windows" { + plainYAML = strings.Replace(plainYAML, "\n", "\r\n", -1) + } + + saveMe := myStruct{"1", "guest", "nopassword", []string{"Work", "Documents", "Music"}} + + // Save format using + config, err := quick.New(&saveMe) + c.Assert(err, IsNil) + c.Assert(config, Not(IsNil)) + + err = config.Save(testYAML) + c.Assert(err, IsNil) + + // Check if the saved structure in actually an YAML format + bytes, err := ioutil.ReadFile(testYAML) + c.Assert(err, IsNil) + + c.Assert(plainYAML, Equals, string(bytes)) + + // Check if the loaded data is the same as the saved one + loadMe := myStruct{} + config, err = quick.New(&loadMe) + err = config.Load(testYAML) + c.Assert(err, IsNil) + + c.Assert(reflect.DeepEqual(saveMe, loadMe), Equals, true) +} + +func (s *MySuite) TestJSONFormat(c *C) { + testJSON := "test.json" + defer os.RemoveAll(testJSON) + + type myStruct struct { + Version string + User string + Password string + Directories []string + } + + plainJSON := `{ + "Version": "1", + "User": "guest", + "Password": "nopassword", + "Directories": [ + "Work", + "Documents", + "Music" + ] +}` + + if runtime.GOOS == "windows" { + plainJSON = strings.Replace(plainJSON, "\n", "\r\n", -1) + } + + saveMe := myStruct{"1", "guest", "nopassword", []string{"Work", "Documents", "Music"}} + + // Save format using + config, err := quick.New(&saveMe) + c.Assert(err, IsNil) + c.Assert(config, Not(IsNil)) + + err = config.Save(testJSON) + c.Assert(err, IsNil) + + // Check if the saved structure in actually an JSON format + bytes, err := ioutil.ReadFile(testJSON) + c.Assert(err, IsNil) + + c.Assert(plainJSON, Equals, string(bytes)) + + // Check if the loaded data is the same as the saved one + loadMe := myStruct{} + config, err = quick.New(&loadMe) + err = config.Load(testJSON) + c.Assert(err, IsNil) + + c.Assert(reflect.DeepEqual(saveMe, loadMe), Equals, true) +} + func (s *MySuite) TestVersion(c *C) { defer os.RemoveAll("test.json") type myStruct struct { diff --git a/pkg/sys/rlimit-file_bsd.go b/pkg/sys/rlimit-file_bsd.go new file mode 100644 index 000000000..ca0c8b28f --- /dev/null +++ b/pkg/sys/rlimit-file_bsd.go @@ -0,0 +1,40 @@ +// +build freebsd dragonfly + +/* + * Minio Cloud Storage, (C) 2017 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package sys + +import ( + "syscall" +) + +// GetMaxOpenFileLimit - returns maximum file descriptor number that can be opened by this process. +func GetMaxOpenFileLimit() (curLimit, maxLimit uint64, err error) { + var rlimit syscall.Rlimit + if err = syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rlimit); err == nil { + curLimit = uint64(rlimit.Cur) + maxLimit = uint64(rlimit.Max) + } + + return curLimit, maxLimit, err +} + +// SetMaxOpenFileLimit - sets maximum file descriptor number that can be opened by this process. +func SetMaxOpenFileLimit(curLimit, maxLimit uint64) error { + rlimit := syscall.Rlimit{Cur: int64(curLimit), Max: int64(curLimit)} + return syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rlimit) +} diff --git a/pkg/sys/rlimit-file_nix.go b/pkg/sys/rlimit-file_nix.go new file mode 100644 index 000000000..f6940c66c --- /dev/null +++ b/pkg/sys/rlimit-file_nix.go @@ -0,0 +1,38 @@ +// +build linux darwin openbsd netbsd + +/* + * Minio Cloud Storage, (C) 2017 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package sys + +import "syscall" + +// GetMaxOpenFileLimit - returns maximum file descriptor number that can be opened by this process. +func GetMaxOpenFileLimit() (curLimit, maxLimit uint64, err error) { + var rlimit syscall.Rlimit + if err = syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rlimit); err == nil { + curLimit = rlimit.Cur + maxLimit = rlimit.Max + } + + return curLimit, maxLimit, err +} + +// SetMaxOpenFileLimit - sets maximum file descriptor number that can be opened by this process. +func SetMaxOpenFileLimit(curLimit, maxLimit uint64) error { + rlimit := syscall.Rlimit{Cur: curLimit, Max: maxLimit} + return syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rlimit) +} diff --git a/pkg/sys/rlimit-file_test.go b/pkg/sys/rlimit-file_test.go new file mode 100644 index 000000000..e26cd9c24 --- /dev/null +++ b/pkg/sys/rlimit-file_test.go @@ -0,0 +1,40 @@ +/* + * Minio Cloud Storage, (C) 2017 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package sys + +import "testing" + +// Test get max open file limit. +func TestGetMaxOpenFileLimit(t *testing.T) { + _, _, err := GetMaxOpenFileLimit() + if err != nil { + t.Errorf("expected: nil, got: %v", err) + } +} + +// Test set open file limit +func TestSetMaxOpenFileLimit(t *testing.T) { + curLimit, maxLimit, err := GetMaxOpenFileLimit() + if err != nil { + t.Fatalf("Unable to get max open file limit. %v", err) + } + + err = SetMaxOpenFileLimit(curLimit, maxLimit) + if err != nil { + t.Errorf("expected: nil, got: %v", err) + } +} diff --git a/cmd/commands_test.go b/pkg/sys/rlimit-file_windows.go similarity index 57% rename from cmd/commands_test.go rename to pkg/sys/rlimit-file_windows.go index 8f7e5cb00..ed50f42d7 100644 --- a/cmd/commands_test.go +++ b/pkg/sys/rlimit-file_windows.go @@ -1,3 +1,5 @@ +// +build windows + /* * Minio Cloud Storage, (C) 2017 Minio, Inc. * @@ -14,28 +16,16 @@ * limitations under the License. */ -package cmd +package sys -import ( - "testing" - - "github.com/minio/cli" -) - -// Tests register command function. -func TestRegisterCommand(t *testing.T) { - registerCommand(cli.Command{ - Name: "test1", - }) - ccount := len(commands) - if ccount != 1 { - t.Fatalf("Unexpected number of commands found %d", ccount) - } - registerCommand(cli.Command{ - Name: "test2", - }) - ccount = len(commands) - if ccount != 2 { - t.Fatalf("Unexpected number of commands found %d", ccount) - } +// GetMaxOpenFileLimit - returns maximum file descriptor number that can be opened by this process. +func GetMaxOpenFileLimit() (curLimit, maxLimit uint64, err error) { + // Nothing to do for windows. + return curLimit, maxLimit, err +} + +// SetMaxOpenFileLimit - sets maximum file descriptor number that can be opened by this process. +func SetMaxOpenFileLimit(curLimit, maxLimit uint64) error { + // Nothing to do for windows. + return nil } diff --git a/pkg/sys/rlimit-memory_bsd.go b/pkg/sys/rlimit-memory_bsd.go new file mode 100644 index 000000000..078577b4a --- /dev/null +++ b/pkg/sys/rlimit-memory_bsd.go @@ -0,0 +1,38 @@ +// +build freebsd dragonfly + +/* + * Minio Cloud Storage, (C) 2017 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package sys + +import "syscall" + +// GetMaxMemoryLimit - returns the maximum size of the process's virtual memory (address space) in bytes. +func GetMaxMemoryLimit() (curLimit, maxLimit uint64, err error) { + var rlimit syscall.Rlimit + if err = syscall.Getrlimit(syscall.RLIMIT_DATA, &rlimit); err == nil { + curLimit = uint64(rlimit.Cur) + maxLimit = uint64(rlimit.Max) + } + + return curLimit, maxLimit, err +} + +// SetMaxMemoryLimit - sets the maximum size of the process's virtual memory (address space) in bytes. +func SetMaxMemoryLimit(curLimit, maxLimit uint64) error { + rlimit := syscall.Rlimit{Cur: int64(curLimit), Max: int64(maxLimit)} + return syscall.Setrlimit(syscall.RLIMIT_DATA, &rlimit) +} diff --git a/pkg/sys/rlimit-memory_nix.go b/pkg/sys/rlimit-memory_nix.go new file mode 100644 index 000000000..797b4e187 --- /dev/null +++ b/pkg/sys/rlimit-memory_nix.go @@ -0,0 +1,38 @@ +// +build linux darwin netbsd + +/* + * Minio Cloud Storage, (C) 2017 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package sys + +import "syscall" + +// GetMaxMemoryLimit - returns the maximum size of the process's virtual memory (address space) in bytes. +func GetMaxMemoryLimit() (curLimit, maxLimit uint64, err error) { + var rlimit syscall.Rlimit + if err = syscall.Getrlimit(syscall.RLIMIT_AS, &rlimit); err == nil { + curLimit = rlimit.Cur + maxLimit = rlimit.Max + } + + return curLimit, maxLimit, err +} + +// SetMaxMemoryLimit - sets the maximum size of the process's virtual memory (address space) in bytes. +func SetMaxMemoryLimit(curLimit, maxLimit uint64) error { + rlimit := syscall.Rlimit{Cur: curLimit, Max: maxLimit} + return syscall.Setrlimit(syscall.RLIMIT_AS, &rlimit) +} diff --git a/pkg/sys/rlimit-memory_openbsd.go b/pkg/sys/rlimit-memory_openbsd.go new file mode 100644 index 000000000..83e8d858e --- /dev/null +++ b/pkg/sys/rlimit-memory_openbsd.go @@ -0,0 +1,38 @@ +// +build openbsd + +/* + * Minio Cloud Storage, (C) 2017 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package sys + +import "syscall" + +// GetMaxMemoryLimit - returns the maximum size of the process's virtual memory (address space) in bytes. +func GetMaxMemoryLimit() (curLimit, maxLimit uint64, err error) { + var rlimit syscall.Rlimit + if err = syscall.Getrlimit(syscall.RLIMIT_DATA, &rlimit); err == nil { + curLimit = rlimit.Cur + maxLimit = rlimit.Max + } + + return curLimit, maxLimit, err +} + +// SetMaxMemoryLimit - sets the maximum size of the process's virtual memory (address space) in bytes. +func SetMaxMemoryLimit(curLimit, maxLimit uint64) error { + rlimit := syscall.Rlimit{Cur: curLimit, Max: maxLimit} + return syscall.Setrlimit(syscall.RLIMIT_DATA, &rlimit) +} diff --git a/pkg/sys/rlimit-memory_test.go b/pkg/sys/rlimit-memory_test.go new file mode 100644 index 000000000..b01385004 --- /dev/null +++ b/pkg/sys/rlimit-memory_test.go @@ -0,0 +1,40 @@ +/* + * Minio Cloud Storage, (C) 2017 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package sys + +import "testing" + +// Test get max memory limit. +func TestGetMaxMemoryLimit(t *testing.T) { + _, _, err := GetMaxMemoryLimit() + if err != nil { + t.Errorf("expected: nil, got: %v", err) + } +} + +// Test set memory limit +func TestSetMaxMemoryLimit(t *testing.T) { + curLimit, maxLimit, err := GetMaxMemoryLimit() + if err != nil { + t.Fatalf("Unable to get max memory limit. %v", err) + } + + err = SetMaxMemoryLimit(curLimit, maxLimit) + if err != nil { + t.Errorf("expected: nil, got: %v", err) + } +} diff --git a/pkg/sys/rlimit-memory_windows.go b/pkg/sys/rlimit-memory_windows.go new file mode 100644 index 000000000..0a900d636 --- /dev/null +++ b/pkg/sys/rlimit-memory_windows.go @@ -0,0 +1,31 @@ +// +build windows + +/* + * Minio Cloud Storage, (C) 2017 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package sys + +// GetMaxMemoryLimit - returns the maximum size of the process's virtual memory (address space) in bytes. +func GetMaxMemoryLimit() (curLimit, maxLimit uint64, err error) { + // Nothing to do for windows. + return curLimit, maxLimit, err +} + +// SetMaxMemoryLimit - sets the maximum size of the process's virtual memory (address space) in bytes. +func SetMaxMemoryLimit(curLimit, maxLimit uint64) error { + // Nothing to do for windows. + return nil +} diff --git a/pkg/sys/stats.go b/pkg/sys/stats.go index ea89c7ec9..87aaabad9 100644 --- a/pkg/sys/stats.go +++ b/pkg/sys/stats.go @@ -1,5 +1,5 @@ /* - * Minio Cloud Storage, (C) 2016 Minio, Inc. + * Minio Cloud Storage, (C) 2016, 2017 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,11 +16,6 @@ package sys -import "errors" - -// ErrNotImplemented - GetStats() is not implemented on bsds. -var ErrNotImplemented = errors.New("not implemented") - // Stats - system statistics. type Stats struct { TotalRAM uint64 // Physical RAM size in bytes, diff --git a/pkg/sys/stats_bsd.go b/pkg/sys/stats_bsd.go index 276d0c01b..fa6488971 100644 --- a/pkg/sys/stats_bsd.go +++ b/pkg/sys/stats_bsd.go @@ -1,7 +1,7 @@ -// +build !linux,!windows,!darwin +// +build openbsd netbsd freebsd dragonfly /* - * Minio Cloud Storage, (C) 2016 Minio, Inc. + * Minio Cloud Storage, (C) 2016,2017 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,28 @@ package sys -// GetStats - return system statistics for windows. -func GetStats() (stats Stats, err error) { - return Stats{}, ErrNotImplemented +import ( + "encoding/binary" + "syscall" +) + +func getHwPhysmem() (uint64, error) { + totalString, err := syscall.Sysctl("hw.physmem") + if err != nil { + return 0, err + } + + // syscall.sysctl() helpfully assumes the result is a null-terminated string and + // removes the last byte of the result if it's 0 :/ + totalString += "\x00" + + total := uint64(binary.LittleEndian.Uint64([]byte(totalString))) + + return total, nil +} + +// GetStats - return system statistics for bsd. +func GetStats() (stats Stats, err error) { + stats.TotalRAM, err = getHwPhysmem() + return stats, err } diff --git a/pkg/sys/stats_darwin.go b/pkg/sys/stats_darwin.go index 98d9dcc31..01fae14a0 100644 --- a/pkg/sys/stats_darwin.go +++ b/pkg/sys/stats_darwin.go @@ -1,7 +1,7 @@ // +build darwin /* - * Minio Cloud Storage, (C) 2016 Minio, Inc. + * Minio Cloud Storage, (C) 2016,2017 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -38,14 +38,8 @@ func getHwMemsize() (uint64, error) { return total, nil } -// GetStats - return system statistics for windows. +// GetStats - return system statistics for macOS. func GetStats() (stats Stats, err error) { - memSize, err := getHwMemsize() - if err != nil { - return Stats{}, err - } - stats = Stats{ - TotalRAM: memSize, - } - return stats, nil + stats.TotalRAM, err = getHwMemsize() + return stats, err } diff --git a/pkg/sys/stats_linux.go b/pkg/sys/stats_linux.go index 090235bc4..6cd5c6002 100644 --- a/pkg/sys/stats_linux.go +++ b/pkg/sys/stats_linux.go @@ -1,7 +1,7 @@ // +build linux /* - * Minio Cloud Storage, (C) 2016 Minio, Inc. + * Minio Cloud Storage, (C) 2016,2017 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,13 +22,10 @@ import "syscall" // GetStats - return system statistics. func GetStats() (stats Stats, err error) { - si := syscall.Sysinfo_t{} - err = syscall.Sysinfo(&si) - if err != nil { - return + var si syscall.Sysinfo_t + if err = syscall.Sysinfo(&si); err == nil { + stats.TotalRAM = uint64(si.Totalram) } - stats = Stats{ - TotalRAM: uint64(si.Totalram), - } - return stats, nil + + return stats, err } diff --git a/pkg/sys/stats_test.go b/pkg/sys/stats_test.go index 1a713aed8..e2666639e 100644 --- a/pkg/sys/stats_test.go +++ b/pkg/sys/stats_test.go @@ -1,4 +1,18 @@ -// +build linux darwin windows +/* + * Minio Cloud Storage, (C) 2016,2017 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package sys diff --git a/pkg/sys/stats_windows.go b/pkg/sys/stats_windows.go index 7ae7b9437..08d168428 100644 --- a/pkg/sys/stats_windows.go +++ b/pkg/sys/stats_windows.go @@ -1,7 +1,7 @@ // +build windows /* - * Minio Cloud Storage, (C) 2016 Minio, Inc. + * Minio Cloud Storage, (C) 2016,2017 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -44,12 +44,11 @@ type memoryStatusEx struct { func GetStats() (stats Stats, err error) { var memInfo memoryStatusEx memInfo.cbSize = uint32(unsafe.Sizeof(memInfo)) - mem, _, _ := procGlobalMemoryStatusEx.Call(uintptr(unsafe.Pointer(&memInfo))) - if mem == 0 { - return Stats{}, syscall.GetLastError() + if mem, _, _ := procGlobalMemoryStatusEx.Call(uintptr(unsafe.Pointer(&memInfo))); mem == 0 { + err = syscall.GetLastError() + } else { + stats.TotalRAM = memInfo.ullTotalPhys } - stats = Stats{ - TotalRAM: memInfo.ullTotalPhys, - } - return stats, nil + + return stats, err } diff --git a/vendor/github.com/minio/redigo/LICENSE b/vendor/github.com/Azure/azure-sdk-for-go/LICENSE similarity index 89% rename from vendor/github.com/minio/redigo/LICENSE rename to vendor/github.com/Azure/azure-sdk-for-go/LICENSE index 67db85882..af39a91e7 100644 --- a/vendor/github.com/minio/redigo/LICENSE +++ b/vendor/github.com/Azure/azure-sdk-for-go/LICENSE @@ -173,3 +173,30 @@ defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2016 Microsoft Corporation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/Azure/azure-sdk-for-go/storage/README.md b/vendor/github.com/Azure/azure-sdk-for-go/storage/README.md new file mode 100644 index 000000000..0ab099848 --- /dev/null +++ b/vendor/github.com/Azure/azure-sdk-for-go/storage/README.md @@ -0,0 +1,5 @@ +# Azure Storage SDK for Go + +The `github.com/Azure/azure-sdk-for-go/storage` package is used to perform operations in Azure Storage Service. To manage your storage accounts (Azure Resource Manager / ARM), use the [github.com/Azure/azure-sdk-for-go/arm/storage](../arm/storage) package. For your classic storage accounts (Azure Service Management / ASM), use [github.com/Azure/azure-sdk-for-go/management/storageservice](../management/storageservice) package. + +This package includes support for [Azure Storage Emulator](https://azure.microsoft.com/documentation/articles/storage-use-emulator/) \ No newline at end of file diff --git a/vendor/github.com/Azure/azure-sdk-for-go/storage/authorization.go b/vendor/github.com/Azure/azure-sdk-for-go/storage/authorization.go new file mode 100644 index 000000000..89a0d0b3c --- /dev/null +++ b/vendor/github.com/Azure/azure-sdk-for-go/storage/authorization.go @@ -0,0 +1,223 @@ +// Package storage provides clients for Microsoft Azure Storage Services. +package storage + +import ( + "bytes" + "fmt" + "net/url" + "sort" + "strings" +) + +// See: https://docs.microsoft.com/rest/api/storageservices/fileservices/authentication-for-the-azure-storage-services + +type authentication string + +const ( + sharedKey authentication = "sharedKey" + sharedKeyForTable authentication = "sharedKeyTable" + sharedKeyLite authentication = "sharedKeyLite" + sharedKeyLiteForTable authentication = "sharedKeyLiteTable" + + // headers + headerAuthorization = "Authorization" + headerContentLength = "Content-Length" + headerDate = "Date" + headerXmsDate = "x-ms-date" + headerXmsVersion = "x-ms-version" + headerContentEncoding = "Content-Encoding" + headerContentLanguage = "Content-Language" + headerContentType = "Content-Type" + headerContentMD5 = "Content-MD5" + headerIfModifiedSince = "If-Modified-Since" + headerIfMatch = "If-Match" + headerIfNoneMatch = "If-None-Match" + headerIfUnmodifiedSince = "If-Unmodified-Since" + headerRange = "Range" +) + +func (c *Client) addAuthorizationHeader(verb, url string, headers map[string]string, auth authentication) (map[string]string, error) { + authHeader, err := c.getSharedKey(verb, url, headers, auth) + if err != nil { + return nil, err + } + headers[headerAuthorization] = authHeader + return headers, nil +} + +func (c *Client) getSharedKey(verb, url string, headers map[string]string, auth authentication) (string, error) { + canRes, err := c.buildCanonicalizedResource(url, auth) + if err != nil { + return "", err + } + + canString, err := buildCanonicalizedString(verb, headers, canRes, auth) + if err != nil { + return "", err + } + return c.createAuthorizationHeader(canString, auth), nil +} + +func (c *Client) buildCanonicalizedResource(uri string, auth authentication) (string, error) { + errMsg := "buildCanonicalizedResource error: %s" + u, err := url.Parse(uri) + if err != nil { + return "", fmt.Errorf(errMsg, err.Error()) + } + + cr := bytes.NewBufferString("/") + cr.WriteString(c.getCanonicalizedAccountName()) + + if len(u.Path) > 0 { + // Any portion of the CanonicalizedResource string that is derived from + // the resource's URI should be encoded exactly as it is in the URI. + // -- https://msdn.microsoft.com/en-gb/library/azure/dd179428.aspx + cr.WriteString(u.EscapedPath()) + } + + params, err := url.ParseQuery(u.RawQuery) + if err != nil { + return "", fmt.Errorf(errMsg, err.Error()) + } + + // See https://github.com/Azure/azure-storage-net/blob/master/Lib/Common/Core/Util/AuthenticationUtility.cs#L277 + if auth == sharedKey { + if len(params) > 0 { + cr.WriteString("\n") + + keys := []string{} + for key := range params { + keys = append(keys, key) + } + sort.Strings(keys) + + completeParams := []string{} + for _, key := range keys { + if len(params[key]) > 1 { + sort.Strings(params[key]) + } + + completeParams = append(completeParams, fmt.Sprintf("%s:%s", key, strings.Join(params[key], ","))) + } + cr.WriteString(strings.Join(completeParams, "\n")) + } + } else { + // search for "comp" parameter, if exists then add it to canonicalizedresource + if v, ok := params["comp"]; ok { + cr.WriteString("?comp=" + v[0]) + } + } + + return string(cr.Bytes()), nil +} + +func (c *Client) getCanonicalizedAccountName() string { + // since we may be trying to access a secondary storage account, we need to + // remove the -secondary part of the storage name + return strings.TrimSuffix(c.accountName, "-secondary") +} + +func buildCanonicalizedString(verb string, headers map[string]string, canonicalizedResource string, auth authentication) (string, error) { + contentLength := headers[headerContentLength] + if contentLength == "0" { + contentLength = "" + } + date := headers[headerDate] + if v, ok := headers[headerXmsDate]; ok { + if auth == sharedKey || auth == sharedKeyLite { + date = "" + } else { + date = v + } + } + var canString string + switch auth { + case sharedKey: + canString = strings.Join([]string{ + verb, + headers[headerContentEncoding], + headers[headerContentLanguage], + contentLength, + headers[headerContentMD5], + headers[headerContentType], + date, + headers[headerIfModifiedSince], + headers[headerIfMatch], + headers[headerIfNoneMatch], + headers[headerIfUnmodifiedSince], + headers[headerRange], + buildCanonicalizedHeader(headers), + canonicalizedResource, + }, "\n") + case sharedKeyForTable: + canString = strings.Join([]string{ + verb, + headers[headerContentMD5], + headers[headerContentType], + date, + canonicalizedResource, + }, "\n") + case sharedKeyLite: + canString = strings.Join([]string{ + verb, + headers[headerContentMD5], + headers[headerContentType], + date, + buildCanonicalizedHeader(headers), + canonicalizedResource, + }, "\n") + case sharedKeyLiteForTable: + canString = strings.Join([]string{ + date, + canonicalizedResource, + }, "\n") + default: + return "", fmt.Errorf("%s authentication is not supported yet", auth) + } + return canString, nil +} + +func buildCanonicalizedHeader(headers map[string]string) string { + cm := make(map[string]string) + + for k, v := range headers { + headerName := strings.TrimSpace(strings.ToLower(k)) + if strings.HasPrefix(headerName, "x-ms-") { + cm[headerName] = v + } + } + + if len(cm) == 0 { + return "" + } + + keys := []string{} + for key := range cm { + keys = append(keys, key) + } + + sort.Strings(keys) + + ch := bytes.NewBufferString("") + + for _, key := range keys { + ch.WriteString(key) + ch.WriteRune(':') + ch.WriteString(cm[key]) + ch.WriteRune('\n') + } + + return strings.TrimSuffix(string(ch.Bytes()), "\n") +} + +func (c *Client) createAuthorizationHeader(canonicalizedString string, auth authentication) string { + signature := c.computeHmac256(canonicalizedString) + var key string + switch auth { + case sharedKey, sharedKeyForTable: + key = "SharedKey" + case sharedKeyLite, sharedKeyLiteForTable: + key = "SharedKeyLite" + } + return fmt.Sprintf("%s %s:%s", key, c.getCanonicalizedAccountName(), signature) +} diff --git a/vendor/github.com/Azure/azure-sdk-for-go/storage/blob.go b/vendor/github.com/Azure/azure-sdk-for-go/storage/blob.go new file mode 100644 index 000000000..e33de1031 --- /dev/null +++ b/vendor/github.com/Azure/azure-sdk-for-go/storage/blob.go @@ -0,0 +1,1539 @@ +package storage + +import ( + "bytes" + "encoding/xml" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "strconv" + "strings" + "time" +) + +// BlobStorageClient contains operations for Microsoft Azure Blob Storage +// Service. +type BlobStorageClient struct { + client Client + auth authentication +} + +// A Container is an entry in ContainerListResponse. +type Container struct { + Name string `xml:"Name"` + Properties ContainerProperties `xml:"Properties"` + // TODO (ahmetalpbalkan) Metadata +} + +// ContainerProperties contains various properties of a container returned from +// various endpoints like ListContainers. +type ContainerProperties struct { + LastModified string `xml:"Last-Modified"` + Etag string `xml:"Etag"` + LeaseStatus string `xml:"LeaseStatus"` + LeaseState string `xml:"LeaseState"` + LeaseDuration string `xml:"LeaseDuration"` + // TODO (ahmetalpbalkan) remaining fields +} + +// ContainerListResponse contains the response fields from +// ListContainers call. +// +// See https://msdn.microsoft.com/en-us/library/azure/dd179352.aspx +type ContainerListResponse struct { + XMLName xml.Name `xml:"EnumerationResults"` + Xmlns string `xml:"xmlns,attr"` + Prefix string `xml:"Prefix"` + Marker string `xml:"Marker"` + NextMarker string `xml:"NextMarker"` + MaxResults int64 `xml:"MaxResults"` + Containers []Container `xml:"Containers>Container"` +} + +// A Blob is an entry in BlobListResponse. +type Blob struct { + Name string `xml:"Name"` + Properties BlobProperties `xml:"Properties"` + Metadata BlobMetadata `xml:"Metadata"` +} + +// BlobMetadata is a set of custom name/value pairs. +// +// See https://msdn.microsoft.com/en-us/library/azure/dd179404.aspx +type BlobMetadata map[string]string + +type blobMetadataEntries struct { + Entries []blobMetadataEntry `xml:",any"` +} +type blobMetadataEntry struct { + XMLName xml.Name + Value string `xml:",chardata"` +} + +// UnmarshalXML converts the xml:Metadata into Metadata map +func (bm *BlobMetadata) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + var entries blobMetadataEntries + if err := d.DecodeElement(&entries, &start); err != nil { + return err + } + for _, entry := range entries.Entries { + if *bm == nil { + *bm = make(BlobMetadata) + } + (*bm)[strings.ToLower(entry.XMLName.Local)] = entry.Value + } + return nil +} + +// MarshalXML implements the xml.Marshaler interface. It encodes +// metadata name/value pairs as they would appear in an Azure +// ListBlobs response. +func (bm BlobMetadata) MarshalXML(enc *xml.Encoder, start xml.StartElement) error { + entries := make([]blobMetadataEntry, 0, len(bm)) + for k, v := range bm { + entries = append(entries, blobMetadataEntry{ + XMLName: xml.Name{Local: http.CanonicalHeaderKey(k)}, + Value: v, + }) + } + return enc.EncodeElement(blobMetadataEntries{ + Entries: entries, + }, start) +} + +// BlobProperties contains various properties of a blob +// returned in various endpoints like ListBlobs or GetBlobProperties. +type BlobProperties struct { + LastModified string `xml:"Last-Modified"` + Etag string `xml:"Etag"` + ContentMD5 string `xml:"Content-MD5"` + ContentLength int64 `xml:"Content-Length"` + ContentType string `xml:"Content-Type"` + ContentEncoding string `xml:"Content-Encoding"` + CacheControl string `xml:"Cache-Control"` + ContentLanguage string `xml:"Cache-Language"` + BlobType BlobType `xml:"x-ms-blob-blob-type"` + SequenceNumber int64 `xml:"x-ms-blob-sequence-number"` + CopyID string `xml:"CopyId"` + CopyStatus string `xml:"CopyStatus"` + CopySource string `xml:"CopySource"` + CopyProgress string `xml:"CopyProgress"` + CopyCompletionTime string `xml:"CopyCompletionTime"` + CopyStatusDescription string `xml:"CopyStatusDescription"` + LeaseStatus string `xml:"LeaseStatus"` + LeaseState string `xml:"LeaseState"` +} + +// BlobHeaders contains various properties of a blob and is an entry +// in SetBlobProperties +type BlobHeaders struct { + ContentMD5 string `header:"x-ms-blob-content-md5"` + ContentLanguage string `header:"x-ms-blob-content-language"` + ContentEncoding string `header:"x-ms-blob-content-encoding"` + ContentType string `header:"x-ms-blob-content-type"` + CacheControl string `header:"x-ms-blob-cache-control"` +} + +// BlobListResponse contains the response fields from ListBlobs call. +// +// See https://msdn.microsoft.com/en-us/library/azure/dd135734.aspx +type BlobListResponse struct { + XMLName xml.Name `xml:"EnumerationResults"` + Xmlns string `xml:"xmlns,attr"` + Prefix string `xml:"Prefix"` + Marker string `xml:"Marker"` + NextMarker string `xml:"NextMarker"` + MaxResults int64 `xml:"MaxResults"` + Blobs []Blob `xml:"Blobs>Blob"` + + // BlobPrefix is used to traverse blobs as if it were a file system. + // It is returned if ListBlobsParameters.Delimiter is specified. + // The list here can be thought of as "folders" that may contain + // other folders or blobs. + BlobPrefixes []string `xml:"Blobs>BlobPrefix>Name"` + + // Delimiter is used to traverse blobs as if it were a file system. + // It is returned if ListBlobsParameters.Delimiter is specified. + Delimiter string `xml:"Delimiter"` +} + +// ListContainersParameters defines the set of customizable parameters to make a +// List Containers call. +// +// See https://msdn.microsoft.com/en-us/library/azure/dd179352.aspx +type ListContainersParameters struct { + Prefix string + Marker string + Include string + MaxResults uint + Timeout uint +} + +func (p ListContainersParameters) getParameters() url.Values { + out := url.Values{} + + if p.Prefix != "" { + out.Set("prefix", p.Prefix) + } + if p.Marker != "" { + out.Set("marker", p.Marker) + } + if p.Include != "" { + out.Set("include", p.Include) + } + if p.MaxResults != 0 { + out.Set("maxresults", fmt.Sprintf("%v", p.MaxResults)) + } + if p.Timeout != 0 { + out.Set("timeout", fmt.Sprintf("%v", p.Timeout)) + } + + return out +} + +// ListBlobsParameters defines the set of customizable +// parameters to make a List Blobs call. +// +// See https://msdn.microsoft.com/en-us/library/azure/dd135734.aspx +type ListBlobsParameters struct { + Prefix string + Delimiter string + Marker string + Include string + MaxResults uint + Timeout uint +} + +func (p ListBlobsParameters) getParameters() url.Values { + out := url.Values{} + + if p.Prefix != "" { + out.Set("prefix", p.Prefix) + } + if p.Delimiter != "" { + out.Set("delimiter", p.Delimiter) + } + if p.Marker != "" { + out.Set("marker", p.Marker) + } + if p.Include != "" { + out.Set("include", p.Include) + } + if p.MaxResults != 0 { + out.Set("maxresults", fmt.Sprintf("%v", p.MaxResults)) + } + if p.Timeout != 0 { + out.Set("timeout", fmt.Sprintf("%v", p.Timeout)) + } + + return out +} + +// BlobType defines the type of the Azure Blob. +type BlobType string + +// Types of page blobs +const ( + BlobTypeBlock BlobType = "BlockBlob" + BlobTypePage BlobType = "PageBlob" + BlobTypeAppend BlobType = "AppendBlob" +) + +// PageWriteType defines the type updates that are going to be +// done on the page blob. +type PageWriteType string + +// Types of operations on page blobs +const ( + PageWriteTypeUpdate PageWriteType = "update" + PageWriteTypeClear PageWriteType = "clear" +) + +const ( + blobCopyStatusPending = "pending" + blobCopyStatusSuccess = "success" + blobCopyStatusAborted = "aborted" + blobCopyStatusFailed = "failed" +) + +// lease constants. +const ( + leaseHeaderPrefix = "x-ms-lease-" + headerLeaseID = "x-ms-lease-id" + leaseAction = "x-ms-lease-action" + leaseBreakPeriod = "x-ms-lease-break-period" + leaseDuration = "x-ms-lease-duration" + leaseProposedID = "x-ms-proposed-lease-id" + leaseTime = "x-ms-lease-time" + + acquireLease = "acquire" + renewLease = "renew" + changeLease = "change" + releaseLease = "release" + breakLease = "break" +) + +// BlockListType is used to filter out types of blocks in a Get Blocks List call +// for a block blob. +// +// See https://msdn.microsoft.com/en-us/library/azure/dd179400.aspx for all +// block types. +type BlockListType string + +// Filters for listing blocks in block blobs +const ( + BlockListTypeAll BlockListType = "all" + BlockListTypeCommitted BlockListType = "committed" + BlockListTypeUncommitted BlockListType = "uncommitted" +) + +// ContainerAccessType defines the access level to the container from a public +// request. +// +// See https://msdn.microsoft.com/en-us/library/azure/dd179468.aspx and "x-ms- +// blob-public-access" header. +type ContainerAccessType string + +// Access options for containers +const ( + ContainerAccessTypePrivate ContainerAccessType = "" + ContainerAccessTypeBlob ContainerAccessType = "blob" + ContainerAccessTypeContainer ContainerAccessType = "container" +) + +// ContainerAccessPolicyDetails are used for SETTING container policies +type ContainerAccessPolicyDetails struct { + ID string + StartTime time.Time + ExpiryTime time.Time + CanRead bool + CanWrite bool + CanDelete bool +} + +// ContainerPermissions is used when setting permissions and Access Policies for containers. +type ContainerPermissions struct { + AccessType ContainerAccessType + AccessPolicies []ContainerAccessPolicyDetails +} + +// ContainerAccessHeader references header used when setting/getting container ACL +const ( + ContainerAccessHeader string = "x-ms-blob-public-access" +) + +// Maximum sizes (per REST API) for various concepts +const ( + MaxBlobBlockSize = 4 * 1024 * 1024 + MaxBlobPageSize = 4 * 1024 * 1024 +) + +// BlockStatus defines states a block for a block blob can +// be in. +type BlockStatus string + +// List of statuses that can be used to refer to a block in a block list +const ( + BlockStatusUncommitted BlockStatus = "Uncommitted" + BlockStatusCommitted BlockStatus = "Committed" + BlockStatusLatest BlockStatus = "Latest" +) + +// Block is used to create Block entities for Put Block List +// call. +type Block struct { + ID string + Status BlockStatus +} + +// BlockListResponse contains the response fields from Get Block List call. +// +// See https://msdn.microsoft.com/en-us/library/azure/dd179400.aspx +type BlockListResponse struct { + XMLName xml.Name `xml:"BlockList"` + CommittedBlocks []BlockResponse `xml:"CommittedBlocks>Block"` + UncommittedBlocks []BlockResponse `xml:"UncommittedBlocks>Block"` +} + +// BlockResponse contains the block information returned +// in the GetBlockListCall. +type BlockResponse struct { + Name string `xml:"Name"` + Size int64 `xml:"Size"` +} + +// GetPageRangesResponse contains the response fields from +// Get Page Ranges call. +// +// See https://msdn.microsoft.com/en-us/library/azure/ee691973.aspx +type GetPageRangesResponse struct { + XMLName xml.Name `xml:"PageList"` + PageList []PageRange `xml:"PageRange"` +} + +// PageRange contains information about a page of a page blob from +// Get Pages Range call. +// +// See https://msdn.microsoft.com/en-us/library/azure/ee691973.aspx +type PageRange struct { + Start int64 `xml:"Start"` + End int64 `xml:"End"` +} + +var ( + errBlobCopyAborted = errors.New("storage: blob copy is aborted") + errBlobCopyIDMismatch = errors.New("storage: blob copy id is a mismatch") +) + +// ListContainers returns the list of containers in a storage account along with +// pagination token and other response details. +// +// See https://msdn.microsoft.com/en-us/library/azure/dd179352.aspx +func (b BlobStorageClient) ListContainers(params ListContainersParameters) (ContainerListResponse, error) { + q := mergeParams(params.getParameters(), url.Values{"comp": {"list"}}) + uri := b.client.getEndpoint(blobServiceName, "", q) + headers := b.client.getStandardHeaders() + + var out ContainerListResponse + resp, err := b.client.exec(http.MethodGet, uri, headers, nil, b.auth) + if err != nil { + return out, err + } + defer resp.body.Close() + + err = xmlUnmarshal(resp.body, &out) + return out, err +} + +// CreateContainer creates a blob container within the storage account +// with given name and access level. Returns error if container already exists. +// +// See https://msdn.microsoft.com/en-us/library/azure/dd179468.aspx +func (b BlobStorageClient) CreateContainer(name string, access ContainerAccessType) error { + resp, err := b.createContainer(name, access) + if err != nil { + return err + } + defer resp.body.Close() + return checkRespCode(resp.statusCode, []int{http.StatusCreated}) +} + +// CreateContainerIfNotExists creates a blob container if it does not exist. Returns +// true if container is newly created or false if container already exists. +func (b BlobStorageClient) CreateContainerIfNotExists(name string, access ContainerAccessType) (bool, error) { + resp, err := b.createContainer(name, access) + if resp != nil { + defer resp.body.Close() + if resp.statusCode == http.StatusCreated || resp.statusCode == http.StatusConflict { + return resp.statusCode == http.StatusCreated, nil + } + } + return false, err +} + +func (b BlobStorageClient) createContainer(name string, access ContainerAccessType) (*storageResponse, error) { + uri := b.client.getEndpoint(blobServiceName, pathForContainer(name), url.Values{"restype": {"container"}}) + + headers := b.client.getStandardHeaders() + if access != "" { + headers[ContainerAccessHeader] = string(access) + } + return b.client.exec(http.MethodPut, uri, headers, nil, b.auth) +} + +// ContainerExists returns true if a container with given name exists +// on the storage account, otherwise returns false. +func (b BlobStorageClient) ContainerExists(name string) (bool, error) { + uri := b.client.getEndpoint(blobServiceName, pathForContainer(name), url.Values{"restype": {"container"}}) + headers := b.client.getStandardHeaders() + + resp, err := b.client.exec(http.MethodHead, uri, headers, nil, b.auth) + if resp != nil { + defer resp.body.Close() + if resp.statusCode == http.StatusOK || resp.statusCode == http.StatusNotFound { + return resp.statusCode == http.StatusOK, nil + } + } + return false, err +} + +// SetContainerPermissions sets up container permissions as per https://msdn.microsoft.com/en-us/library/azure/dd179391.aspx +func (b BlobStorageClient) SetContainerPermissions(container string, containerPermissions ContainerPermissions, timeout int, leaseID string) (err error) { + params := url.Values{ + "restype": {"container"}, + "comp": {"acl"}, + } + + if timeout > 0 { + params.Add("timeout", strconv.Itoa(timeout)) + } + + uri := b.client.getEndpoint(blobServiceName, pathForContainer(container), params) + headers := b.client.getStandardHeaders() + if containerPermissions.AccessType != "" { + headers[ContainerAccessHeader] = string(containerPermissions.AccessType) + } + + if leaseID != "" { + headers[headerLeaseID] = leaseID + } + + body, length, err := generateContainerACLpayload(containerPermissions.AccessPolicies) + headers["Content-Length"] = strconv.Itoa(length) + resp, err := b.client.exec(http.MethodPut, uri, headers, body, b.auth) + + if err != nil { + return err + } + + if resp != nil { + defer resp.body.Close() + + if resp.statusCode != http.StatusOK { + return errors.New("Unable to set permissions") + } + } + return nil +} + +// GetContainerPermissions gets the container permissions as per https://msdn.microsoft.com/en-us/library/azure/dd179469.aspx +// If timeout is 0 then it will not be passed to Azure +// leaseID will only be passed to Azure if populated +// Returns permissionResponse which is combined permissions and AccessPolicy +func (b BlobStorageClient) GetContainerPermissions(container string, timeout int, leaseID string) (*ContainerPermissions, error) { + params := url.Values{"restype": {"container"}, + "comp": {"acl"}} + + if timeout > 0 { + params.Add("timeout", strconv.Itoa(timeout)) + } + + uri := b.client.getEndpoint(blobServiceName, pathForContainer(container), params) + headers := b.client.getStandardHeaders() + + if leaseID != "" { + headers[headerLeaseID] = leaseID + } + + resp, err := b.client.exec(http.MethodGet, uri, headers, nil, b.auth) + if err != nil { + return nil, err + } + defer resp.body.Close() + + var out AccessPolicy + err = xmlUnmarshal(resp.body, &out.SignedIdentifiersList) + if err != nil { + return nil, err + } + + permissionResponse := updateContainerAccessPolicy(out, &resp.headers) + return &permissionResponse, nil +} + +func updateContainerAccessPolicy(ap AccessPolicy, headers *http.Header) ContainerPermissions { + // containerAccess. Blob, Container, empty + containerAccess := headers.Get(http.CanonicalHeaderKey(ContainerAccessHeader)) + + var cp ContainerPermissions + cp.AccessType = ContainerAccessType(containerAccess) + for _, policy := range ap.SignedIdentifiersList.SignedIdentifiers { + capd := ContainerAccessPolicyDetails{ + ID: policy.ID, + StartTime: policy.AccessPolicy.StartTime, + ExpiryTime: policy.AccessPolicy.ExpiryTime, + } + capd.CanRead = updatePermissions(policy.AccessPolicy.Permission, "r") + capd.CanWrite = updatePermissions(policy.AccessPolicy.Permission, "w") + capd.CanDelete = updatePermissions(policy.AccessPolicy.Permission, "d") + + cp.AccessPolicies = append(cp.AccessPolicies, capd) + } + + return cp +} + +// DeleteContainer deletes the container with given name on the storage +// account. If the container does not exist returns error. +// +// See https://msdn.microsoft.com/en-us/library/azure/dd179408.aspx +func (b BlobStorageClient) DeleteContainer(name string) error { + resp, err := b.deleteContainer(name) + if err != nil { + return err + } + defer resp.body.Close() + return checkRespCode(resp.statusCode, []int{http.StatusAccepted}) +} + +// DeleteContainerIfExists deletes the container with given name on the storage +// account if it exists. Returns true if container is deleted with this call, or +// false if the container did not exist at the time of the Delete Container +// operation. +// +// See https://msdn.microsoft.com/en-us/library/azure/dd179408.aspx +func (b BlobStorageClient) DeleteContainerIfExists(name string) (bool, error) { + resp, err := b.deleteContainer(name) + if resp != nil { + defer resp.body.Close() + if resp.statusCode == http.StatusAccepted || resp.statusCode == http.StatusNotFound { + return resp.statusCode == http.StatusAccepted, nil + } + } + return false, err +} + +func (b BlobStorageClient) deleteContainer(name string) (*storageResponse, error) { + uri := b.client.getEndpoint(blobServiceName, pathForContainer(name), url.Values{"restype": {"container"}}) + + headers := b.client.getStandardHeaders() + return b.client.exec(http.MethodDelete, uri, headers, nil, b.auth) +} + +// ListBlobs returns an object that contains list of blobs in the container, +// pagination token and other information in the response of List Blobs call. +// +// See https://msdn.microsoft.com/en-us/library/azure/dd135734.aspx +func (b BlobStorageClient) ListBlobs(container string, params ListBlobsParameters) (BlobListResponse, error) { + q := mergeParams(params.getParameters(), url.Values{ + "restype": {"container"}, + "comp": {"list"}}) + uri := b.client.getEndpoint(blobServiceName, pathForContainer(container), q) + headers := b.client.getStandardHeaders() + + var out BlobListResponse + resp, err := b.client.exec(http.MethodGet, uri, headers, nil, b.auth) + if err != nil { + return out, err + } + defer resp.body.Close() + + err = xmlUnmarshal(resp.body, &out) + return out, err +} + +// BlobExists returns true if a blob with given name exists on the specified +// container of the storage account. +func (b BlobStorageClient) BlobExists(container, name string) (bool, error) { + uri := b.client.getEndpoint(blobServiceName, pathForBlob(container, name), url.Values{}) + headers := b.client.getStandardHeaders() + resp, err := b.client.exec(http.MethodHead, uri, headers, nil, b.auth) + if resp != nil { + defer resp.body.Close() + if resp.statusCode == http.StatusOK || resp.statusCode == http.StatusNotFound { + return resp.statusCode == http.StatusOK, nil + } + } + return false, err +} + +// GetBlobURL gets the canonical URL to the blob with the specified name in the +// specified container. This method does not create a publicly accessible URL if +// the blob or container is private and this method does not check if the blob +// exists. +func (b BlobStorageClient) GetBlobURL(container, name string) string { + if container == "" { + container = "$root" + } + return b.client.getEndpoint(blobServiceName, pathForBlob(container, name), url.Values{}) +} + +// GetBlob returns a stream to read the blob. Caller must call Close() the +// reader to close on the underlying connection. +// +// See https://msdn.microsoft.com/en-us/library/azure/dd179440.aspx +func (b BlobStorageClient) GetBlob(container, name string) (io.ReadCloser, error) { + resp, err := b.getBlobRange(container, name, "", nil) + if err != nil { + return nil, err + } + + if err := checkRespCode(resp.statusCode, []int{http.StatusOK}); err != nil { + return nil, err + } + return resp.body, nil +} + +// GetBlobRange reads the specified range of a blob to a stream. The bytesRange +// string must be in a format like "0-", "10-100" as defined in HTTP 1.1 spec. +// +// See https://msdn.microsoft.com/en-us/library/azure/dd179440.aspx +func (b BlobStorageClient) GetBlobRange(container, name, bytesRange string, extraHeaders map[string]string) (io.ReadCloser, error) { + resp, err := b.getBlobRange(container, name, bytesRange, extraHeaders) + if err != nil { + return nil, err + } + + if err := checkRespCode(resp.statusCode, []int{http.StatusPartialContent}); err != nil { + return nil, err + } + return resp.body, nil +} + +func (b BlobStorageClient) getBlobRange(container, name, bytesRange string, extraHeaders map[string]string) (*storageResponse, error) { + uri := b.client.getEndpoint(blobServiceName, pathForBlob(container, name), url.Values{}) + + extraHeaders = b.client.protectUserAgent(extraHeaders) + headers := b.client.getStandardHeaders() + if bytesRange != "" { + headers["Range"] = fmt.Sprintf("bytes=%s", bytesRange) + } + + for k, v := range extraHeaders { + headers[k] = v + } + + resp, err := b.client.exec(http.MethodGet, uri, headers, nil, b.auth) + if err != nil { + return nil, err + } + return resp, err +} + +// leasePut is common PUT code for the various acquire/release/break etc functions. +func (b BlobStorageClient) leaseCommonPut(container string, name string, headers map[string]string, expectedStatus int) (http.Header, error) { + params := url.Values{"comp": {"lease"}} + uri := b.client.getEndpoint(blobServiceName, pathForBlob(container, name), params) + + resp, err := b.client.exec(http.MethodPut, uri, headers, nil, b.auth) + if err != nil { + return nil, err + } + defer resp.body.Close() + + if err := checkRespCode(resp.statusCode, []int{expectedStatus}); err != nil { + return nil, err + } + + return resp.headers, nil +} + +// SnapshotBlob creates a snapshot for a blob as per https://msdn.microsoft.com/en-us/library/azure/ee691971.aspx +func (b BlobStorageClient) SnapshotBlob(container string, name string, timeout int, extraHeaders map[string]string) (snapshotTimestamp *time.Time, err error) { + extraHeaders = b.client.protectUserAgent(extraHeaders) + headers := b.client.getStandardHeaders() + params := url.Values{"comp": {"snapshot"}} + + if timeout > 0 { + params.Add("timeout", strconv.Itoa(timeout)) + } + + for k, v := range extraHeaders { + headers[k] = v + } + + uri := b.client.getEndpoint(blobServiceName, pathForBlob(container, name), params) + resp, err := b.client.exec(http.MethodPut, uri, headers, nil, b.auth) + if err != nil { + return nil, err + } + + if err := checkRespCode(resp.statusCode, []int{http.StatusCreated}); err != nil { + return nil, err + } + + snapshotResponse := resp.headers.Get(http.CanonicalHeaderKey("x-ms-snapshot")) + if snapshotResponse != "" { + snapshotTimestamp, err := time.Parse(time.RFC3339, snapshotResponse) + if err != nil { + return nil, err + } + + return &snapshotTimestamp, nil + } + + return nil, errors.New("Snapshot not created") +} + +// AcquireLease creates a lease for a blob as per https://msdn.microsoft.com/en-us/library/azure/ee691972.aspx +// returns leaseID acquired +func (b BlobStorageClient) AcquireLease(container string, name string, leaseTimeInSeconds int, proposedLeaseID string) (returnedLeaseID string, err error) { + headers := b.client.getStandardHeaders() + headers[leaseAction] = acquireLease + + if leaseTimeInSeconds > 0 { + headers[leaseDuration] = strconv.Itoa(leaseTimeInSeconds) + } + + if proposedLeaseID != "" { + headers[leaseProposedID] = proposedLeaseID + } + + respHeaders, err := b.leaseCommonPut(container, name, headers, http.StatusCreated) + if err != nil { + return "", err + } + + returnedLeaseID = respHeaders.Get(http.CanonicalHeaderKey(headerLeaseID)) + + if returnedLeaseID != "" { + return returnedLeaseID, nil + } + + return "", errors.New("LeaseID not returned") +} + +// BreakLease breaks the lease for a blob as per https://msdn.microsoft.com/en-us/library/azure/ee691972.aspx +// Returns the timeout remaining in the lease in seconds +func (b BlobStorageClient) BreakLease(container string, name string) (breakTimeout int, err error) { + headers := b.client.getStandardHeaders() + headers[leaseAction] = breakLease + return b.breakLeaseCommon(container, name, headers) +} + +// BreakLeaseWithBreakPeriod breaks the lease for a blob as per https://msdn.microsoft.com/en-us/library/azure/ee691972.aspx +// breakPeriodInSeconds is used to determine how long until new lease can be created. +// Returns the timeout remaining in the lease in seconds +func (b BlobStorageClient) BreakLeaseWithBreakPeriod(container string, name string, breakPeriodInSeconds int) (breakTimeout int, err error) { + headers := b.client.getStandardHeaders() + headers[leaseAction] = breakLease + headers[leaseBreakPeriod] = strconv.Itoa(breakPeriodInSeconds) + return b.breakLeaseCommon(container, name, headers) +} + +// breakLeaseCommon is common code for both version of BreakLease (with and without break period) +func (b BlobStorageClient) breakLeaseCommon(container string, name string, headers map[string]string) (breakTimeout int, err error) { + + respHeaders, err := b.leaseCommonPut(container, name, headers, http.StatusAccepted) + if err != nil { + return 0, err + } + + breakTimeoutStr := respHeaders.Get(http.CanonicalHeaderKey(leaseTime)) + if breakTimeoutStr != "" { + breakTimeout, err = strconv.Atoi(breakTimeoutStr) + if err != nil { + return 0, err + } + } + + return breakTimeout, nil +} + +// ChangeLease changes a lease ID for a blob as per https://msdn.microsoft.com/en-us/library/azure/ee691972.aspx +// Returns the new LeaseID acquired +func (b BlobStorageClient) ChangeLease(container string, name string, currentLeaseID string, proposedLeaseID string) (newLeaseID string, err error) { + headers := b.client.getStandardHeaders() + headers[leaseAction] = changeLease + headers[headerLeaseID] = currentLeaseID + headers[leaseProposedID] = proposedLeaseID + + respHeaders, err := b.leaseCommonPut(container, name, headers, http.StatusOK) + if err != nil { + return "", err + } + + newLeaseID = respHeaders.Get(http.CanonicalHeaderKey(headerLeaseID)) + if newLeaseID != "" { + return newLeaseID, nil + } + + return "", errors.New("LeaseID not returned") +} + +// ReleaseLease releases the lease for a blob as per https://msdn.microsoft.com/en-us/library/azure/ee691972.aspx +func (b BlobStorageClient) ReleaseLease(container string, name string, currentLeaseID string) error { + headers := b.client.getStandardHeaders() + headers[leaseAction] = releaseLease + headers[headerLeaseID] = currentLeaseID + + _, err := b.leaseCommonPut(container, name, headers, http.StatusOK) + if err != nil { + return err + } + + return nil +} + +// RenewLease renews the lease for a blob as per https://msdn.microsoft.com/en-us/library/azure/ee691972.aspx +func (b BlobStorageClient) RenewLease(container string, name string, currentLeaseID string) error { + headers := b.client.getStandardHeaders() + headers[leaseAction] = renewLease + headers[headerLeaseID] = currentLeaseID + + _, err := b.leaseCommonPut(container, name, headers, http.StatusOK) + if err != nil { + return err + } + + return nil +} + +// GetBlobProperties provides various information about the specified +// blob. See https://msdn.microsoft.com/en-us/library/azure/dd179394.aspx +func (b BlobStorageClient) GetBlobProperties(container, name string) (*BlobProperties, error) { + uri := b.client.getEndpoint(blobServiceName, pathForBlob(container, name), url.Values{}) + + headers := b.client.getStandardHeaders() + resp, err := b.client.exec(http.MethodHead, uri, headers, nil, b.auth) + if err != nil { + return nil, err + } + defer resp.body.Close() + + if err = checkRespCode(resp.statusCode, []int{http.StatusOK}); err != nil { + return nil, err + } + + var contentLength int64 + contentLengthStr := resp.headers.Get("Content-Length") + if contentLengthStr != "" { + contentLength, err = strconv.ParseInt(contentLengthStr, 0, 64) + if err != nil { + return nil, err + } + } + + var sequenceNum int64 + sequenceNumStr := resp.headers.Get("x-ms-blob-sequence-number") + if sequenceNumStr != "" { + sequenceNum, err = strconv.ParseInt(sequenceNumStr, 0, 64) + if err != nil { + return nil, err + } + } + + return &BlobProperties{ + LastModified: resp.headers.Get("Last-Modified"), + Etag: resp.headers.Get("Etag"), + ContentMD5: resp.headers.Get("Content-MD5"), + ContentLength: contentLength, + ContentEncoding: resp.headers.Get("Content-Encoding"), + ContentType: resp.headers.Get("Content-Type"), + CacheControl: resp.headers.Get("Cache-Control"), + ContentLanguage: resp.headers.Get("Content-Language"), + SequenceNumber: sequenceNum, + CopyCompletionTime: resp.headers.Get("x-ms-copy-completion-time"), + CopyStatusDescription: resp.headers.Get("x-ms-copy-status-description"), + CopyID: resp.headers.Get("x-ms-copy-id"), + CopyProgress: resp.headers.Get("x-ms-copy-progress"), + CopySource: resp.headers.Get("x-ms-copy-source"), + CopyStatus: resp.headers.Get("x-ms-copy-status"), + BlobType: BlobType(resp.headers.Get("x-ms-blob-type")), + LeaseStatus: resp.headers.Get("x-ms-lease-status"), + LeaseState: resp.headers.Get("x-ms-lease-state"), + }, nil +} + +// SetBlobProperties replaces the BlobHeaders for the specified blob. +// +// Some keys may be converted to Camel-Case before sending. All keys +// are returned in lower case by GetBlobProperties. HTTP header names +// are case-insensitive so case munging should not matter to other +// applications either. +// +// See https://msdn.microsoft.com/en-us/library/azure/ee691966.aspx +func (b BlobStorageClient) SetBlobProperties(container, name string, blobHeaders BlobHeaders) error { + params := url.Values{"comp": {"properties"}} + uri := b.client.getEndpoint(blobServiceName, pathForBlob(container, name), params) + headers := b.client.getStandardHeaders() + + extraHeaders := headersFromStruct(blobHeaders) + + for k, v := range extraHeaders { + headers[k] = v + } + + resp, err := b.client.exec(http.MethodPut, uri, headers, nil, b.auth) + if err != nil { + return err + } + defer resp.body.Close() + + return checkRespCode(resp.statusCode, []int{http.StatusOK}) +} + +// SetBlobMetadata replaces the metadata for the specified blob. +// +// Some keys may be converted to Camel-Case before sending. All keys +// are returned in lower case by GetBlobMetadata. HTTP header names +// are case-insensitive so case munging should not matter to other +// applications either. +// +// See https://msdn.microsoft.com/en-us/library/azure/dd179414.aspx +func (b BlobStorageClient) SetBlobMetadata(container, name string, metadata map[string]string, extraHeaders map[string]string) error { + params := url.Values{"comp": {"metadata"}} + uri := b.client.getEndpoint(blobServiceName, pathForBlob(container, name), params) + metadata = b.client.protectUserAgent(metadata) + extraHeaders = b.client.protectUserAgent(extraHeaders) + headers := b.client.getStandardHeaders() + for k, v := range metadata { + headers[userDefinedMetadataHeaderPrefix+k] = v + } + + for k, v := range extraHeaders { + headers[k] = v + } + + resp, err := b.client.exec(http.MethodPut, uri, headers, nil, b.auth) + if err != nil { + return err + } + defer resp.body.Close() + + return checkRespCode(resp.statusCode, []int{http.StatusOK}) +} + +// GetBlobMetadata returns all user-defined metadata for the specified blob. +// +// All metadata keys will be returned in lower case. (HTTP header +// names are case-insensitive.) +// +// See https://msdn.microsoft.com/en-us/library/azure/dd179414.aspx +func (b BlobStorageClient) GetBlobMetadata(container, name string) (map[string]string, error) { + params := url.Values{"comp": {"metadata"}} + uri := b.client.getEndpoint(blobServiceName, pathForBlob(container, name), params) + headers := b.client.getStandardHeaders() + + resp, err := b.client.exec(http.MethodGet, uri, headers, nil, b.auth) + if err != nil { + return nil, err + } + defer resp.body.Close() + + if err := checkRespCode(resp.statusCode, []int{http.StatusOK}); err != nil { + return nil, err + } + + metadata := make(map[string]string) + for k, v := range resp.headers { + // Can't trust CanonicalHeaderKey() to munge case + // reliably. "_" is allowed in identifiers: + // https://msdn.microsoft.com/en-us/library/azure/dd179414.aspx + // https://msdn.microsoft.com/library/aa664670(VS.71).aspx + // http://tools.ietf.org/html/rfc7230#section-3.2 + // ...but "_" is considered invalid by + // CanonicalMIMEHeaderKey in + // https://golang.org/src/net/textproto/reader.go?s=14615:14659#L542 + // so k can be "X-Ms-Meta-Foo" or "x-ms-meta-foo_bar". + k = strings.ToLower(k) + if len(v) == 0 || !strings.HasPrefix(k, strings.ToLower(userDefinedMetadataHeaderPrefix)) { + continue + } + // metadata["foo"] = content of the last X-Ms-Meta-Foo header + k = k[len(userDefinedMetadataHeaderPrefix):] + metadata[k] = v[len(v)-1] + } + return metadata, nil +} + +// CreateBlockBlob initializes an empty block blob with no blocks. +// +// See https://msdn.microsoft.com/en-us/library/azure/dd179451.aspx +func (b BlobStorageClient) CreateBlockBlob(container, name string) error { + return b.CreateBlockBlobFromReader(container, name, 0, nil, nil) +} + +// CreateBlockBlobFromReader initializes a block blob using data from +// reader. Size must be the number of bytes read from reader. To +// create an empty blob, use size==0 and reader==nil. +// +// The API rejects requests with size > 64 MiB (but this limit is not +// checked by the SDK). To write a larger blob, use CreateBlockBlob, +// PutBlock, and PutBlockList. +// +// See https://msdn.microsoft.com/en-us/library/azure/dd179451.aspx +func (b BlobStorageClient) CreateBlockBlobFromReader(container, name string, size uint64, blob io.Reader, extraHeaders map[string]string) error { + path := fmt.Sprintf("%s/%s", container, name) + uri := b.client.getEndpoint(blobServiceName, path, url.Values{}) + extraHeaders = b.client.protectUserAgent(extraHeaders) + headers := b.client.getStandardHeaders() + headers["x-ms-blob-type"] = string(BlobTypeBlock) + headers["Content-Length"] = fmt.Sprintf("%d", size) + + for k, v := range extraHeaders { + headers[k] = v + } + + resp, err := b.client.exec(http.MethodPut, uri, headers, blob, b.auth) + if err != nil { + return err + } + defer resp.body.Close() + return checkRespCode(resp.statusCode, []int{http.StatusCreated}) +} + +// PutBlock saves the given data chunk to the specified block blob with +// given ID. +// +// The API rejects chunks larger than 4 MiB (but this limit is not +// checked by the SDK). +// +// See https://msdn.microsoft.com/en-us/library/azure/dd135726.aspx +func (b BlobStorageClient) PutBlock(container, name, blockID string, chunk []byte) error { + return b.PutBlockWithLength(container, name, blockID, uint64(len(chunk)), bytes.NewReader(chunk), nil) +} + +// PutBlockWithLength saves the given data stream of exactly specified size to +// the block blob with given ID. It is an alternative to PutBlocks where data +// comes as stream but the length is known in advance. +// +// The API rejects requests with size > 4 MiB (but this limit is not +// checked by the SDK). +// +// See https://msdn.microsoft.com/en-us/library/azure/dd135726.aspx +func (b BlobStorageClient) PutBlockWithLength(container, name, blockID string, size uint64, blob io.Reader, extraHeaders map[string]string) error { + uri := b.client.getEndpoint(blobServiceName, pathForBlob(container, name), url.Values{"comp": {"block"}, "blockid": {blockID}}) + extraHeaders = b.client.protectUserAgent(extraHeaders) + headers := b.client.getStandardHeaders() + headers["x-ms-blob-type"] = string(BlobTypeBlock) + headers["Content-Length"] = fmt.Sprintf("%v", size) + + for k, v := range extraHeaders { + headers[k] = v + } + + resp, err := b.client.exec(http.MethodPut, uri, headers, blob, b.auth) + if err != nil { + return err + } + + defer resp.body.Close() + return checkRespCode(resp.statusCode, []int{http.StatusCreated}) +} + +// PutBlockList saves list of blocks to the specified block blob. +// +// See https://msdn.microsoft.com/en-us/library/azure/dd179467.aspx +func (b BlobStorageClient) PutBlockList(container, name string, blocks []Block) error { + blockListXML := prepareBlockListRequest(blocks) + + uri := b.client.getEndpoint(blobServiceName, pathForBlob(container, name), url.Values{"comp": {"blocklist"}}) + headers := b.client.getStandardHeaders() + headers["Content-Length"] = fmt.Sprintf("%v", len(blockListXML)) + + resp, err := b.client.exec(http.MethodPut, uri, headers, strings.NewReader(blockListXML), b.auth) + if err != nil { + return err + } + defer resp.body.Close() + return checkRespCode(resp.statusCode, []int{http.StatusCreated}) +} + +// GetBlockList retrieves list of blocks in the specified block blob. +// +// See https://msdn.microsoft.com/en-us/library/azure/dd179400.aspx +func (b BlobStorageClient) GetBlockList(container, name string, blockType BlockListType) (BlockListResponse, error) { + params := url.Values{"comp": {"blocklist"}, "blocklisttype": {string(blockType)}} + uri := b.client.getEndpoint(blobServiceName, pathForBlob(container, name), params) + headers := b.client.getStandardHeaders() + + var out BlockListResponse + resp, err := b.client.exec(http.MethodGet, uri, headers, nil, b.auth) + if err != nil { + return out, err + } + defer resp.body.Close() + + err = xmlUnmarshal(resp.body, &out) + return out, err +} + +// PutPageBlob initializes an empty page blob with specified name and maximum +// size in bytes (size must be aligned to a 512-byte boundary). A page blob must +// be created using this method before writing pages. +// +// See https://msdn.microsoft.com/en-us/library/azure/dd179451.aspx +func (b BlobStorageClient) PutPageBlob(container, name string, size int64, extraHeaders map[string]string) error { + path := fmt.Sprintf("%s/%s", container, name) + uri := b.client.getEndpoint(blobServiceName, path, url.Values{}) + extraHeaders = b.client.protectUserAgent(extraHeaders) + headers := b.client.getStandardHeaders() + headers["x-ms-blob-type"] = string(BlobTypePage) + headers["x-ms-blob-content-length"] = fmt.Sprintf("%v", size) + + for k, v := range extraHeaders { + headers[k] = v + } + + resp, err := b.client.exec(http.MethodPut, uri, headers, nil, b.auth) + if err != nil { + return err + } + defer resp.body.Close() + + return checkRespCode(resp.statusCode, []int{http.StatusCreated}) +} + +// PutPage writes a range of pages to a page blob or clears the given range. +// In case of 'clear' writes, given chunk is discarded. Ranges must be aligned +// with 512-byte boundaries and chunk must be of size multiplies by 512. +// +// See https://msdn.microsoft.com/en-us/library/ee691975.aspx +func (b BlobStorageClient) PutPage(container, name string, startByte, endByte int64, writeType PageWriteType, chunk []byte, extraHeaders map[string]string) error { + path := fmt.Sprintf("%s/%s", container, name) + uri := b.client.getEndpoint(blobServiceName, path, url.Values{"comp": {"page"}}) + extraHeaders = b.client.protectUserAgent(extraHeaders) + headers := b.client.getStandardHeaders() + headers["x-ms-blob-type"] = string(BlobTypePage) + headers["x-ms-page-write"] = string(writeType) + headers["x-ms-range"] = fmt.Sprintf("bytes=%v-%v", startByte, endByte) + for k, v := range extraHeaders { + headers[k] = v + } + var contentLength int64 + var data io.Reader + if writeType == PageWriteTypeClear { + contentLength = 0 + data = bytes.NewReader([]byte{}) + } else { + contentLength = int64(len(chunk)) + data = bytes.NewReader(chunk) + } + headers["Content-Length"] = fmt.Sprintf("%v", contentLength) + + resp, err := b.client.exec(http.MethodPut, uri, headers, data, b.auth) + if err != nil { + return err + } + defer resp.body.Close() + + return checkRespCode(resp.statusCode, []int{http.StatusCreated}) +} + +// GetPageRanges returns the list of valid page ranges for a page blob. +// +// See https://msdn.microsoft.com/en-us/library/azure/ee691973.aspx +func (b BlobStorageClient) GetPageRanges(container, name string) (GetPageRangesResponse, error) { + path := fmt.Sprintf("%s/%s", container, name) + uri := b.client.getEndpoint(blobServiceName, path, url.Values{"comp": {"pagelist"}}) + headers := b.client.getStandardHeaders() + + var out GetPageRangesResponse + resp, err := b.client.exec(http.MethodGet, uri, headers, nil, b.auth) + if err != nil { + return out, err + } + defer resp.body.Close() + + if err = checkRespCode(resp.statusCode, []int{http.StatusOK}); err != nil { + return out, err + } + err = xmlUnmarshal(resp.body, &out) + return out, err +} + +// PutAppendBlob initializes an empty append blob with specified name. An +// append blob must be created using this method before appending blocks. +// +// See https://msdn.microsoft.com/en-us/library/azure/dd179451.aspx +func (b BlobStorageClient) PutAppendBlob(container, name string, extraHeaders map[string]string) error { + path := fmt.Sprintf("%s/%s", container, name) + uri := b.client.getEndpoint(blobServiceName, path, url.Values{}) + extraHeaders = b.client.protectUserAgent(extraHeaders) + headers := b.client.getStandardHeaders() + headers["x-ms-blob-type"] = string(BlobTypeAppend) + + for k, v := range extraHeaders { + headers[k] = v + } + + resp, err := b.client.exec(http.MethodPut, uri, headers, nil, b.auth) + if err != nil { + return err + } + defer resp.body.Close() + + return checkRespCode(resp.statusCode, []int{http.StatusCreated}) +} + +// AppendBlock appends a block to an append blob. +// +// See https://msdn.microsoft.com/en-us/library/azure/mt427365.aspx +func (b BlobStorageClient) AppendBlock(container, name string, chunk []byte, extraHeaders map[string]string) error { + path := fmt.Sprintf("%s/%s", container, name) + uri := b.client.getEndpoint(blobServiceName, path, url.Values{"comp": {"appendblock"}}) + extraHeaders = b.client.protectUserAgent(extraHeaders) + headers := b.client.getStandardHeaders() + headers["x-ms-blob-type"] = string(BlobTypeAppend) + headers["Content-Length"] = fmt.Sprintf("%v", len(chunk)) + + for k, v := range extraHeaders { + headers[k] = v + } + + resp, err := b.client.exec(http.MethodPut, uri, headers, bytes.NewReader(chunk), b.auth) + if err != nil { + return err + } + defer resp.body.Close() + + return checkRespCode(resp.statusCode, []int{http.StatusCreated}) +} + +// CopyBlob starts a blob copy operation and waits for the operation to +// complete. sourceBlob parameter must be a canonical URL to the blob (can be +// obtained using GetBlobURL method.) There is no SLA on blob copy and therefore +// this helper method works faster on smaller files. +// +// See https://msdn.microsoft.com/en-us/library/azure/dd894037.aspx +func (b BlobStorageClient) CopyBlob(container, name, sourceBlob string) error { + copyID, err := b.StartBlobCopy(container, name, sourceBlob) + if err != nil { + return err + } + + return b.WaitForBlobCopy(container, name, copyID) +} + +// StartBlobCopy starts a blob copy operation. +// sourceBlob parameter must be a canonical URL to the blob (can be +// obtained using GetBlobURL method.) +// +// See https://msdn.microsoft.com/en-us/library/azure/dd894037.aspx +func (b BlobStorageClient) StartBlobCopy(container, name, sourceBlob string) (string, error) { + uri := b.client.getEndpoint(blobServiceName, pathForBlob(container, name), url.Values{}) + + headers := b.client.getStandardHeaders() + headers["x-ms-copy-source"] = sourceBlob + + resp, err := b.client.exec(http.MethodPut, uri, headers, nil, b.auth) + if err != nil { + return "", err + } + defer resp.body.Close() + + if err := checkRespCode(resp.statusCode, []int{http.StatusAccepted, http.StatusCreated}); err != nil { + return "", err + } + + copyID := resp.headers.Get("x-ms-copy-id") + if copyID == "" { + return "", errors.New("Got empty copy id header") + } + return copyID, nil +} + +// AbortBlobCopy aborts a BlobCopy which has already been triggered by the StartBlobCopy function. +// copyID is generated from StartBlobCopy function. +// currentLeaseID is required IF the destination blob has an active lease on it. +// As defined in https://msdn.microsoft.com/en-us/library/azure/jj159098.aspx +func (b BlobStorageClient) AbortBlobCopy(container, name, copyID, currentLeaseID string, timeout int) error { + params := url.Values{"comp": {"copy"}, "copyid": {copyID}} + if timeout > 0 { + params.Add("timeout", strconv.Itoa(timeout)) + } + + uri := b.client.getEndpoint(blobServiceName, pathForBlob(container, name), params) + headers := b.client.getStandardHeaders() + headers["x-ms-copy-action"] = "abort" + + if currentLeaseID != "" { + headers[headerLeaseID] = currentLeaseID + } + + resp, err := b.client.exec(http.MethodPut, uri, headers, nil, b.auth) + if err != nil { + return err + } + defer resp.body.Close() + + if err := checkRespCode(resp.statusCode, []int{http.StatusNoContent}); err != nil { + return err + } + + return nil +} + +// WaitForBlobCopy loops until a BlobCopy operation is completed (or fails with error) +func (b BlobStorageClient) WaitForBlobCopy(container, name, copyID string) error { + for { + props, err := b.GetBlobProperties(container, name) + if err != nil { + return err + } + + if props.CopyID != copyID { + return errBlobCopyIDMismatch + } + + switch props.CopyStatus { + case blobCopyStatusSuccess: + return nil + case blobCopyStatusPending: + continue + case blobCopyStatusAborted: + return errBlobCopyAborted + case blobCopyStatusFailed: + return fmt.Errorf("storage: blob copy failed. Id=%s Description=%s", props.CopyID, props.CopyStatusDescription) + default: + return fmt.Errorf("storage: unhandled blob copy status: '%s'", props.CopyStatus) + } + } +} + +// DeleteBlob deletes the given blob from the specified container. +// If the blob does not exists at the time of the Delete Blob operation, it +// returns error. See https://msdn.microsoft.com/en-us/library/azure/dd179413.aspx +func (b BlobStorageClient) DeleteBlob(container, name string, extraHeaders map[string]string) error { + resp, err := b.deleteBlob(container, name, extraHeaders) + if err != nil { + return err + } + defer resp.body.Close() + return checkRespCode(resp.statusCode, []int{http.StatusAccepted}) +} + +// DeleteBlobIfExists deletes the given blob from the specified container If the +// blob is deleted with this call, returns true. Otherwise returns false. +// +// See https://msdn.microsoft.com/en-us/library/azure/dd179413.aspx +func (b BlobStorageClient) DeleteBlobIfExists(container, name string, extraHeaders map[string]string) (bool, error) { + resp, err := b.deleteBlob(container, name, extraHeaders) + if resp != nil { + defer resp.body.Close() + if resp.statusCode == http.StatusAccepted || resp.statusCode == http.StatusNotFound { + return resp.statusCode == http.StatusAccepted, nil + } + } + return false, err +} + +func (b BlobStorageClient) deleteBlob(container, name string, extraHeaders map[string]string) (*storageResponse, error) { + uri := b.client.getEndpoint(blobServiceName, pathForBlob(container, name), url.Values{}) + extraHeaders = b.client.protectUserAgent(extraHeaders) + headers := b.client.getStandardHeaders() + for k, v := range extraHeaders { + headers[k] = v + } + + return b.client.exec(http.MethodDelete, uri, headers, nil, b.auth) +} + +// helper method to construct the path to a container given its name +func pathForContainer(name string) string { + return fmt.Sprintf("/%s", name) +} + +// helper method to construct the path to a blob given its container and blob +// name +func pathForBlob(container, name string) string { + return fmt.Sprintf("/%s/%s", container, name) +} + +// GetBlobSASURIWithSignedIPAndProtocol creates an URL to the specified blob which contains the Shared +// Access Signature with specified permissions and expiration time. Also includes signedIPRange and allowed protocols. +// If old API version is used but no signedIP is passed (ie empty string) then this should still work. +// We only populate the signedIP when it non-empty. +// +// See https://msdn.microsoft.com/en-us/library/azure/ee395415.aspx +func (b BlobStorageClient) GetBlobSASURIWithSignedIPAndProtocol(container, name string, expiry time.Time, permissions string, signedIPRange string, HTTPSOnly bool) (string, error) { + var ( + signedPermissions = permissions + blobURL = b.GetBlobURL(container, name) + ) + canonicalizedResource, err := b.client.buildCanonicalizedResource(blobURL, b.auth) + if err != nil { + return "", err + } + + // "The canonicalizedresouce portion of the string is a canonical path to the signed resource. + // It must include the service name (blob, table, queue or file) for version 2015-02-21 or + // later, the storage account name, and the resource name, and must be URL-decoded. + // -- https://msdn.microsoft.com/en-us/library/azure/dn140255.aspx + + // We need to replace + with %2b first to avoid being treated as a space (which is correct for query strings, but not the path component). + canonicalizedResource = strings.Replace(canonicalizedResource, "+", "%2b", -1) + canonicalizedResource, err = url.QueryUnescape(canonicalizedResource) + if err != nil { + return "", err + } + + signedExpiry := expiry.UTC().Format(time.RFC3339) + signedResource := "b" + + protocols := "https,http" + if HTTPSOnly { + protocols = "https" + } + stringToSign, err := blobSASStringToSign(b.client.apiVersion, canonicalizedResource, signedExpiry, signedPermissions, signedIPRange, protocols) + if err != nil { + return "", err + } + + sig := b.client.computeHmac256(stringToSign) + sasParams := url.Values{ + "sv": {b.client.apiVersion}, + "se": {signedExpiry}, + "sr": {signedResource}, + "sp": {signedPermissions}, + "sig": {sig}, + } + + if b.client.apiVersion >= "2015-04-05" { + sasParams.Add("spr", protocols) + if signedIPRange != "" { + sasParams.Add("sip", signedIPRange) + } + } + + sasURL, err := url.Parse(blobURL) + if err != nil { + return "", err + } + sasURL.RawQuery = sasParams.Encode() + return sasURL.String(), nil +} + +// GetBlobSASURI creates an URL to the specified blob which contains the Shared +// Access Signature with specified permissions and expiration time. +// +// See https://msdn.microsoft.com/en-us/library/azure/ee395415.aspx +func (b BlobStorageClient) GetBlobSASURI(container, name string, expiry time.Time, permissions string) (string, error) { + url, err := b.GetBlobSASURIWithSignedIPAndProtocol(container, name, expiry, permissions, "", false) + return url, err +} + +func blobSASStringToSign(signedVersion, canonicalizedResource, signedExpiry, signedPermissions string, signedIP string, protocols string) (string, error) { + var signedStart, signedIdentifier, rscc, rscd, rsce, rscl, rsct string + + if signedVersion >= "2015-02-21" { + canonicalizedResource = "/blob" + canonicalizedResource + } + + // https://msdn.microsoft.com/en-us/library/azure/dn140255.aspx#Anchor_12 + if signedVersion >= "2015-04-05" { + return fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s", signedPermissions, signedStart, signedExpiry, canonicalizedResource, signedIdentifier, signedIP, protocols, signedVersion, rscc, rscd, rsce, rscl, rsct), nil + } + + // reference: http://msdn.microsoft.com/en-us/library/azure/dn140255.aspx + if signedVersion >= "2013-08-15" { + return fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s", signedPermissions, signedStart, signedExpiry, canonicalizedResource, signedIdentifier, signedVersion, rscc, rscd, rsce, rscl, rsct), nil + } + + return "", errors.New("storage: not implemented SAS for versions earlier than 2013-08-15") +} + +func generateContainerACLpayload(policies []ContainerAccessPolicyDetails) (io.Reader, int, error) { + sil := SignedIdentifiers{ + SignedIdentifiers: []SignedIdentifier{}, + } + for _, capd := range policies { + permission := capd.generateContainerPermissions() + signedIdentifier := convertAccessPolicyToXMLStructs(capd.ID, capd.StartTime, capd.ExpiryTime, permission) + sil.SignedIdentifiers = append(sil.SignedIdentifiers, signedIdentifier) + } + return xmlMarshal(sil) +} + +func (capd *ContainerAccessPolicyDetails) generateContainerPermissions() (permissions string) { + // generate the permissions string (rwd). + // still want the end user API to have bool flags. + permissions = "" + + if capd.CanRead { + permissions += "r" + } + + if capd.CanWrite { + permissions += "w" + } + + if capd.CanDelete { + permissions += "d" + } + + return permissions +} diff --git a/vendor/github.com/Azure/azure-sdk-for-go/storage/client.go b/vendor/github.com/Azure/azure-sdk-for-go/storage/client.go new file mode 100644 index 000000000..817f934c5 --- /dev/null +++ b/vendor/github.com/Azure/azure-sdk-for-go/storage/client.go @@ -0,0 +1,469 @@ +// Package storage provides clients for Microsoft Azure Storage Services. +package storage + +import ( + "bytes" + "encoding/base64" + "encoding/json" + "encoding/xml" + "errors" + "fmt" + "io" + "io/ioutil" + "net/http" + "net/url" + "runtime" + "strconv" + "strings" +) + +const ( + // DefaultBaseURL is the domain name used for storage requests when a + // default client is created. + DefaultBaseURL = "core.windows.net" + + // DefaultAPIVersion is the Azure Storage API version string used when a + // basic client is created. + DefaultAPIVersion = "2015-02-21" + + defaultUseHTTPS = true + + // StorageEmulatorAccountName is the fixed storage account used by Azure Storage Emulator + StorageEmulatorAccountName = "devstoreaccount1" + + // StorageEmulatorAccountKey is the the fixed storage account used by Azure Storage Emulator + StorageEmulatorAccountKey = "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==" + + blobServiceName = "blob" + tableServiceName = "table" + queueServiceName = "queue" + fileServiceName = "file" + + storageEmulatorBlob = "127.0.0.1:10000" + storageEmulatorTable = "127.0.0.1:10002" + storageEmulatorQueue = "127.0.0.1:10001" + + userAgentHeader = "User-Agent" +) + +// Client is the object that needs to be constructed to perform +// operations on the storage account. +type Client struct { + // HTTPClient is the http.Client used to initiate API + // requests. If it is nil, http.DefaultClient is used. + HTTPClient *http.Client + + accountName string + accountKey []byte + useHTTPS bool + UseSharedKeyLite bool + baseURL string + apiVersion string + userAgent string +} + +type storageResponse struct { + statusCode int + headers http.Header + body io.ReadCloser +} + +type odataResponse struct { + storageResponse + odata odataErrorMessage +} + +// AzureStorageServiceError contains fields of the error response from +// Azure Storage Service REST API. See https://msdn.microsoft.com/en-us/library/azure/dd179382.aspx +// Some fields might be specific to certain calls. +type AzureStorageServiceError struct { + Code string `xml:"Code"` + Message string `xml:"Message"` + AuthenticationErrorDetail string `xml:"AuthenticationErrorDetail"` + QueryParameterName string `xml:"QueryParameterName"` + QueryParameterValue string `xml:"QueryParameterValue"` + Reason string `xml:"Reason"` + StatusCode int + RequestID string +} + +type odataErrorMessageMessage struct { + Lang string `json:"lang"` + Value string `json:"value"` +} + +type odataErrorMessageInternal struct { + Code string `json:"code"` + Message odataErrorMessageMessage `json:"message"` +} + +type odataErrorMessage struct { + Err odataErrorMessageInternal `json:"odata.error"` +} + +// UnexpectedStatusCodeError is returned when a storage service responds with neither an error +// nor with an HTTP status code indicating success. +type UnexpectedStatusCodeError struct { + allowed []int + got int +} + +func (e UnexpectedStatusCodeError) Error() string { + s := func(i int) string { return fmt.Sprintf("%d %s", i, http.StatusText(i)) } + + got := s(e.got) + expected := []string{} + for _, v := range e.allowed { + expected = append(expected, s(v)) + } + return fmt.Sprintf("storage: status code from service response is %s; was expecting %s", got, strings.Join(expected, " or ")) +} + +// Got is the actual status code returned by Azure. +func (e UnexpectedStatusCodeError) Got() int { + return e.got +} + +// NewBasicClient constructs a Client with given storage service name and +// key. +func NewBasicClient(accountName, accountKey string) (Client, error) { + if accountName == StorageEmulatorAccountName { + return NewEmulatorClient() + } + return NewClient(accountName, accountKey, DefaultBaseURL, DefaultAPIVersion, defaultUseHTTPS) + +} + +//NewEmulatorClient contructs a Client intended to only work with Azure +//Storage Emulator +func NewEmulatorClient() (Client, error) { + return NewClient(StorageEmulatorAccountName, StorageEmulatorAccountKey, DefaultBaseURL, DefaultAPIVersion, false) +} + +// NewClient constructs a Client. This should be used if the caller wants +// to specify whether to use HTTPS, a specific REST API version or a custom +// storage endpoint than Azure Public Cloud. +func NewClient(accountName, accountKey, blobServiceBaseURL, apiVersion string, useHTTPS bool) (Client, error) { + var c Client + if accountName == "" { + return c, fmt.Errorf("azure: account name required") + } else if accountKey == "" { + return c, fmt.Errorf("azure: account key required") + } else if blobServiceBaseURL == "" { + return c, fmt.Errorf("azure: base storage service url required") + } + + key, err := base64.StdEncoding.DecodeString(accountKey) + if err != nil { + return c, fmt.Errorf("azure: malformed storage account key: %v", err) + } + + c = Client{ + accountName: accountName, + accountKey: key, + useHTTPS: useHTTPS, + baseURL: blobServiceBaseURL, + apiVersion: apiVersion, + UseSharedKeyLite: false, + } + c.userAgent = c.getDefaultUserAgent() + return c, nil +} + +func (c Client) getDefaultUserAgent() string { + return fmt.Sprintf("Go/%s (%s-%s) Azure-SDK-For-Go/%s storage-dataplane/%s", + runtime.Version(), + runtime.GOARCH, + runtime.GOOS, + sdkVersion, + c.apiVersion, + ) +} + +// AddToUserAgent adds an extension to the current user agent +func (c *Client) AddToUserAgent(extension string) error { + if extension != "" { + c.userAgent = fmt.Sprintf("%s %s", c.userAgent, extension) + return nil + } + return fmt.Errorf("Extension was empty, User Agent stayed as %s", c.userAgent) +} + +// protectUserAgent is used in funcs that include extraheaders as a parameter. +// It prevents the User-Agent header to be overwritten, instead if it happens to +// be present, it gets added to the current User-Agent. Use it before getStandardHeaders +func (c *Client) protectUserAgent(extraheaders map[string]string) map[string]string { + if v, ok := extraheaders[userAgentHeader]; ok { + c.AddToUserAgent(v) + delete(extraheaders, userAgentHeader) + } + return extraheaders +} + +func (c Client) getBaseURL(service string) string { + scheme := "http" + if c.useHTTPS { + scheme = "https" + } + host := "" + if c.accountName == StorageEmulatorAccountName { + switch service { + case blobServiceName: + host = storageEmulatorBlob + case tableServiceName: + host = storageEmulatorTable + case queueServiceName: + host = storageEmulatorQueue + } + } else { + host = fmt.Sprintf("%s.%s.%s", c.accountName, service, c.baseURL) + } + + u := &url.URL{ + Scheme: scheme, + Host: host} + return u.String() +} + +func (c Client) getEndpoint(service, path string, params url.Values) string { + u, err := url.Parse(c.getBaseURL(service)) + if err != nil { + // really should not be happening + panic(err) + } + + // API doesn't accept path segments not starting with '/' + if !strings.HasPrefix(path, "/") { + path = fmt.Sprintf("/%v", path) + } + + if c.accountName == StorageEmulatorAccountName { + path = fmt.Sprintf("/%v%v", StorageEmulatorAccountName, path) + } + + u.Path = path + u.RawQuery = params.Encode() + return u.String() +} + +// GetBlobService returns a BlobStorageClient which can operate on the blob +// service of the storage account. +func (c Client) GetBlobService() BlobStorageClient { + b := BlobStorageClient{ + client: c, + } + b.client.AddToUserAgent(blobServiceName) + b.auth = sharedKey + if c.UseSharedKeyLite { + b.auth = sharedKeyLite + } + return b +} + +// GetQueueService returns a QueueServiceClient which can operate on the queue +// service of the storage account. +func (c Client) GetQueueService() QueueServiceClient { + q := QueueServiceClient{ + client: c, + } + q.client.AddToUserAgent(queueServiceName) + q.auth = sharedKey + if c.UseSharedKeyLite { + q.auth = sharedKeyLite + } + return q +} + +// GetTableService returns a TableServiceClient which can operate on the table +// service of the storage account. +func (c Client) GetTableService() TableServiceClient { + t := TableServiceClient{ + client: c, + } + t.client.AddToUserAgent(tableServiceName) + t.auth = sharedKeyForTable + if c.UseSharedKeyLite { + t.auth = sharedKeyLiteForTable + } + return t +} + +// GetFileService returns a FileServiceClient which can operate on the file +// service of the storage account. +func (c Client) GetFileService() FileServiceClient { + f := FileServiceClient{ + client: c, + } + f.client.AddToUserAgent(fileServiceName) + f.auth = sharedKey + if c.UseSharedKeyLite { + f.auth = sharedKeyLite + } + return f +} + +func (c Client) getStandardHeaders() map[string]string { + return map[string]string{ + userAgentHeader: c.userAgent, + "x-ms-version": c.apiVersion, + "x-ms-date": currentTimeRfc1123Formatted(), + } +} + +func (c Client) exec(verb, url string, headers map[string]string, body io.Reader, auth authentication) (*storageResponse, error) { + headers, err := c.addAuthorizationHeader(verb, url, headers, auth) + if err != nil { + return nil, err + } + + req, err := http.NewRequest(verb, url, body) + if err != nil { + return nil, errors.New("azure/storage: error creating request: " + err.Error()) + } + + if clstr, ok := headers["Content-Length"]; ok { + // content length header is being signed, but completely ignored by golang. + // instead we have to use the ContentLength property on the request struct + // (see https://golang.org/src/net/http/request.go?s=18140:18370#L536 and + // https://golang.org/src/net/http/transfer.go?s=1739:2467#L49) + req.ContentLength, err = strconv.ParseInt(clstr, 10, 64) + if err != nil { + return nil, err + } + } + for k, v := range headers { + req.Header.Add(k, v) + } + + httpClient := c.HTTPClient + if httpClient == nil { + httpClient = http.DefaultClient + } + resp, err := httpClient.Do(req) + if err != nil { + return nil, err + } + + statusCode := resp.StatusCode + if statusCode >= 400 && statusCode <= 505 { + var respBody []byte + respBody, err = readResponseBody(resp) + if err != nil { + return nil, err + } + + requestID := resp.Header.Get("x-ms-request-id") + if len(respBody) == 0 { + // no error in response body, might happen in HEAD requests + err = serviceErrFromStatusCode(resp.StatusCode, resp.Status, requestID) + } else { + // response contains storage service error object, unmarshal + storageErr, errIn := serviceErrFromXML(respBody, resp.StatusCode, requestID) + if err != nil { // error unmarshaling the error response + err = errIn + } + err = storageErr + } + return &storageResponse{ + statusCode: resp.StatusCode, + headers: resp.Header, + body: ioutil.NopCloser(bytes.NewReader(respBody)), /* restore the body */ + }, err + } + + return &storageResponse{ + statusCode: resp.StatusCode, + headers: resp.Header, + body: resp.Body}, nil +} + +func (c Client) execInternalJSON(verb, url string, headers map[string]string, body io.Reader, auth authentication) (*odataResponse, error) { + headers, err := c.addAuthorizationHeader(verb, url, headers, auth) + if err != nil { + return nil, err + } + + req, err := http.NewRequest(verb, url, body) + for k, v := range headers { + req.Header.Add(k, v) + } + + httpClient := c.HTTPClient + if httpClient == nil { + httpClient = http.DefaultClient + } + + resp, err := httpClient.Do(req) + if err != nil { + return nil, err + } + + respToRet := &odataResponse{} + respToRet.body = resp.Body + respToRet.statusCode = resp.StatusCode + respToRet.headers = resp.Header + + statusCode := resp.StatusCode + if statusCode >= 400 && statusCode <= 505 { + var respBody []byte + respBody, err = readResponseBody(resp) + if err != nil { + return nil, err + } + + if len(respBody) == 0 { + // no error in response body, might happen in HEAD requests + err = serviceErrFromStatusCode(resp.StatusCode, resp.Status, resp.Header.Get("x-ms-request-id")) + return respToRet, err + } + // try unmarshal as odata.error json + err = json.Unmarshal(respBody, &respToRet.odata) + return respToRet, err + } + + return respToRet, nil +} + +func readResponseBody(resp *http.Response) ([]byte, error) { + defer resp.Body.Close() + out, err := ioutil.ReadAll(resp.Body) + if err == io.EOF { + err = nil + } + return out, err +} + +func serviceErrFromXML(body []byte, statusCode int, requestID string) (AzureStorageServiceError, error) { + var storageErr AzureStorageServiceError + if err := xml.Unmarshal(body, &storageErr); err != nil { + return storageErr, err + } + storageErr.StatusCode = statusCode + storageErr.RequestID = requestID + return storageErr, nil +} + +func serviceErrFromStatusCode(code int, status string, requestID string) AzureStorageServiceError { + return AzureStorageServiceError{ + StatusCode: code, + Code: status, + RequestID: requestID, + Message: "no response body was available for error status code", + } +} + +func (e AzureStorageServiceError) Error() string { + return fmt.Sprintf("storage: service returned error: StatusCode=%d, ErrorCode=%s, ErrorMessage=%s, RequestId=%s, QueryParameterName=%s, QueryParameterValue=%s", + e.StatusCode, e.Code, e.Message, e.RequestID, e.QueryParameterName, e.QueryParameterValue) +} + +// checkRespCode returns UnexpectedStatusError if the given response code is not +// one of the allowed status codes; otherwise nil. +func checkRespCode(respCode int, allowed []int) error { + for _, v := range allowed { + if respCode == v { + return nil + } + } + return UnexpectedStatusCodeError{allowed, respCode} +} diff --git a/vendor/github.com/Azure/azure-sdk-for-go/storage/directory.go b/vendor/github.com/Azure/azure-sdk-for-go/storage/directory.go new file mode 100644 index 000000000..d07e0af1c --- /dev/null +++ b/vendor/github.com/Azure/azure-sdk-for-go/storage/directory.go @@ -0,0 +1,217 @@ +package storage + +import ( + "encoding/xml" + "net/http" + "net/url" +) + +// Directory represents a directory on a share. +type Directory struct { + fsc *FileServiceClient + Metadata map[string]string + Name string `xml:"Name"` + parent *Directory + Properties DirectoryProperties + share *Share +} + +// DirectoryProperties contains various properties of a directory. +type DirectoryProperties struct { + LastModified string `xml:"Last-Modified"` + Etag string `xml:"Etag"` +} + +// ListDirsAndFilesParameters defines the set of customizable parameters to +// make a List Files and Directories call. +// +// See https://msdn.microsoft.com/en-us/library/azure/dn166980.aspx +type ListDirsAndFilesParameters struct { + Marker string + MaxResults uint + Timeout uint +} + +// DirsAndFilesListResponse contains the response fields from +// a List Files and Directories call. +// +// See https://msdn.microsoft.com/en-us/library/azure/dn166980.aspx +type DirsAndFilesListResponse struct { + XMLName xml.Name `xml:"EnumerationResults"` + Xmlns string `xml:"xmlns,attr"` + Marker string `xml:"Marker"` + MaxResults int64 `xml:"MaxResults"` + Directories []Directory `xml:"Entries>Directory"` + Files []File `xml:"Entries>File"` + NextMarker string `xml:"NextMarker"` +} + +// builds the complete directory path for this directory object. +func (d *Directory) buildPath() string { + path := "" + current := d + for current.Name != "" { + path = "/" + current.Name + path + current = current.parent + } + return d.share.buildPath() + path +} + +// Create this directory in the associated share. +// If a directory with the same name already exists, the operation fails. +// +// See https://msdn.microsoft.com/en-us/library/azure/dn166993.aspx +func (d *Directory) Create() error { + // if this is the root directory exit early + if d.parent == nil { + return nil + } + + headers, err := d.fsc.createResource(d.buildPath(), resourceDirectory, mergeMDIntoExtraHeaders(d.Metadata, nil)) + if err != nil { + return err + } + + d.updateEtagAndLastModified(headers) + return nil +} + +// CreateIfNotExists creates this directory under the associated share if the +// directory does not exists. Returns true if the directory is newly created or +// false if the directory already exists. +// +// See https://msdn.microsoft.com/en-us/library/azure/dn166993.aspx +func (d *Directory) CreateIfNotExists() (bool, error) { + // if this is the root directory exit early + if d.parent == nil { + return false, nil + } + + resp, err := d.fsc.createResourceNoClose(d.buildPath(), resourceDirectory, nil) + if resp != nil { + defer resp.body.Close() + if resp.statusCode == http.StatusCreated || resp.statusCode == http.StatusConflict { + if resp.statusCode == http.StatusCreated { + d.updateEtagAndLastModified(resp.headers) + return true, nil + } + + return false, d.FetchAttributes() + } + } + + return false, err +} + +// Delete removes this directory. It must be empty in order to be deleted. +// If the directory does not exist the operation fails. +// +// See https://msdn.microsoft.com/en-us/library/azure/dn166969.aspx +func (d *Directory) Delete() error { + return d.fsc.deleteResource(d.buildPath(), resourceDirectory) +} + +// DeleteIfExists removes this directory if it exists. +// +// See https://msdn.microsoft.com/en-us/library/azure/dn166969.aspx +func (d *Directory) DeleteIfExists() (bool, error) { + resp, err := d.fsc.deleteResourceNoClose(d.buildPath(), resourceDirectory) + if resp != nil { + defer resp.body.Close() + if resp.statusCode == http.StatusAccepted || resp.statusCode == http.StatusNotFound { + return resp.statusCode == http.StatusAccepted, nil + } + } + return false, err +} + +// Exists returns true if this directory exists. +func (d *Directory) Exists() (bool, error) { + exists, headers, err := d.fsc.resourceExists(d.buildPath(), resourceDirectory) + if exists { + d.updateEtagAndLastModified(headers) + } + return exists, err +} + +// FetchAttributes retrieves metadata for this directory. +func (d *Directory) FetchAttributes() error { + headers, err := d.fsc.getResourceHeaders(d.buildPath(), compNone, resourceDirectory, http.MethodHead) + if err != nil { + return err + } + + d.updateEtagAndLastModified(headers) + d.Metadata = getMetadataFromHeaders(headers) + + return nil +} + +// GetDirectoryReference returns a child Directory object for this directory. +func (d *Directory) GetDirectoryReference(name string) *Directory { + return &Directory{ + fsc: d.fsc, + Name: name, + parent: d, + share: d.share, + } +} + +// GetFileReference returns a child File object for this directory. +func (d *Directory) GetFileReference(name string) *File { + return &File{ + fsc: d.fsc, + Name: name, + parent: d, + share: d.share, + } +} + +// ListDirsAndFiles returns a list of files and directories under this directory. +// It also contains a pagination token and other response details. +// +// See https://msdn.microsoft.com/en-us/library/azure/dn166980.aspx +func (d *Directory) ListDirsAndFiles(params ListDirsAndFilesParameters) (*DirsAndFilesListResponse, error) { + q := mergeParams(params.getParameters(), getURLInitValues(compList, resourceDirectory)) + + resp, err := d.fsc.listContent(d.buildPath(), q, nil) + if err != nil { + return nil, err + } + + defer resp.body.Close() + var out DirsAndFilesListResponse + err = xmlUnmarshal(resp.body, &out) + return &out, err +} + +// SetMetadata replaces the metadata for this directory. +// +// Some keys may be converted to Camel-Case before sending. All keys +// are returned in lower case by GetDirectoryMetadata. HTTP header names +// are case-insensitive so case munging should not matter to other +// applications either. +// +// See https://msdn.microsoft.com/en-us/library/azure/mt427370.aspx +func (d *Directory) SetMetadata() error { + headers, err := d.fsc.setResourceHeaders(d.buildPath(), compMetadata, resourceDirectory, mergeMDIntoExtraHeaders(d.Metadata, nil)) + if err != nil { + return err + } + + d.updateEtagAndLastModified(headers) + return nil +} + +// updates Etag and last modified date +func (d *Directory) updateEtagAndLastModified(headers http.Header) { + d.Properties.Etag = headers.Get("Etag") + d.Properties.LastModified = headers.Get("Last-Modified") +} + +// URL gets the canonical URL to this directory. +// This method does not create a publicly accessible URL if the directory +// is private and this method does not check if the directory exists. +func (d *Directory) URL() string { + return d.fsc.client.getEndpoint(fileServiceName, d.buildPath(), url.Values{}) +} diff --git a/vendor/github.com/Azure/azure-sdk-for-go/storage/file.go b/vendor/github.com/Azure/azure-sdk-for-go/storage/file.go new file mode 100644 index 000000000..575f3f726 --- /dev/null +++ b/vendor/github.com/Azure/azure-sdk-for-go/storage/file.go @@ -0,0 +1,360 @@ +package storage + +import ( + "errors" + "fmt" + "io" + "net/http" + "net/url" + "strconv" +) + +const fourMB = uint64(4194304) +const oneTB = uint64(1099511627776) + +// File represents a file on a share. +type File struct { + fsc *FileServiceClient + Metadata map[string]string + Name string `xml:"Name"` + parent *Directory + Properties FileProperties `xml:"Properties"` + share *Share +} + +// FileProperties contains various properties of a file. +type FileProperties struct { + CacheControl string `header:"x-ms-cache-control"` + Disposition string `header:"x-ms-content-disposition"` + Encoding string `header:"x-ms-content-encoding"` + Etag string + Language string `header:"x-ms-content-language"` + LastModified string + Length uint64 `xml:"Content-Length"` + MD5 string `header:"x-ms-content-md5"` + Type string `header:"x-ms-content-type"` +} + +// FileCopyState contains various properties of a file copy operation. +type FileCopyState struct { + CompletionTime string + ID string + Progress string + Source string + Status string + StatusDesc string +} + +// FileStream contains file data returned from a call to GetFile. +type FileStream struct { + Body io.ReadCloser + ContentMD5 string +} + +// FileRanges contains a list of file range information for a file. +// +// See https://msdn.microsoft.com/en-us/library/azure/dn166984.aspx +type FileRanges struct { + ContentLength uint64 + LastModified string + ETag string + FileRanges []FileRange `xml:"Range"` +} + +// FileRange contains range information for a file. +// +// See https://msdn.microsoft.com/en-us/library/azure/dn166984.aspx +type FileRange struct { + Start uint64 `xml:"Start"` + End uint64 `xml:"End"` +} + +func (fr FileRange) String() string { + return fmt.Sprintf("bytes=%d-%d", fr.Start, fr.End) +} + +// builds the complete file path for this file object +func (f *File) buildPath() string { + return f.parent.buildPath() + "/" + f.Name +} + +// ClearRange releases the specified range of space in a file. +// +// See https://msdn.microsoft.com/en-us/library/azure/dn194276.aspx +func (f *File) ClearRange(fileRange FileRange) error { + headers, err := f.modifyRange(nil, fileRange, nil) + if err != nil { + return err + } + + f.updateEtagAndLastModified(headers) + return nil +} + +// Create creates a new file or replaces an existing one. +// +// See https://msdn.microsoft.com/en-us/library/azure/dn194271.aspx +func (f *File) Create(maxSize uint64) error { + if maxSize > oneTB { + return fmt.Errorf("max file size is 1TB") + } + + extraHeaders := map[string]string{ + "x-ms-content-length": strconv.FormatUint(maxSize, 10), + "x-ms-type": "file", + } + + headers, err := f.fsc.createResource(f.buildPath(), resourceFile, mergeMDIntoExtraHeaders(f.Metadata, extraHeaders)) + if err != nil { + return err + } + + f.Properties.Length = maxSize + f.updateEtagAndLastModified(headers) + return nil +} + +// Delete immediately removes this file from the storage account. +// +// See https://msdn.microsoft.com/en-us/library/azure/dn689085.aspx +func (f *File) Delete() error { + return f.fsc.deleteResource(f.buildPath(), resourceFile) +} + +// DeleteIfExists removes this file if it exists. +// +// See https://msdn.microsoft.com/en-us/library/azure/dn689085.aspx +func (f *File) DeleteIfExists() (bool, error) { + resp, err := f.fsc.deleteResourceNoClose(f.buildPath(), resourceFile) + if resp != nil { + defer resp.body.Close() + if resp.statusCode == http.StatusAccepted || resp.statusCode == http.StatusNotFound { + return resp.statusCode == http.StatusAccepted, nil + } + } + return false, err +} + +// DownloadRangeToStream operation downloads the specified range of this file with optional MD5 hash. +// +// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/get-file +func (f *File) DownloadRangeToStream(fileRange FileRange, getContentMD5 bool) (fs FileStream, err error) { + if getContentMD5 && isRangeTooBig(fileRange) { + return fs, fmt.Errorf("must specify a range less than or equal to 4MB when getContentMD5 is true") + } + + extraHeaders := map[string]string{ + "Range": fileRange.String(), + } + if getContentMD5 == true { + extraHeaders["x-ms-range-get-content-md5"] = "true" + } + + resp, err := f.fsc.getResourceNoClose(f.buildPath(), compNone, resourceFile, http.MethodGet, extraHeaders) + if err != nil { + return fs, err + } + + if err = checkRespCode(resp.statusCode, []int{http.StatusOK, http.StatusPartialContent}); err != nil { + resp.body.Close() + return fs, err + } + + fs.Body = resp.body + if getContentMD5 { + fs.ContentMD5 = resp.headers.Get("Content-MD5") + } + return fs, nil +} + +// Exists returns true if this file exists. +func (f *File) Exists() (bool, error) { + exists, headers, err := f.fsc.resourceExists(f.buildPath(), resourceFile) + if exists { + f.updateEtagAndLastModified(headers) + f.updateProperties(headers) + } + return exists, err +} + +// FetchAttributes updates metadata and properties for this file. +func (f *File) FetchAttributes() error { + headers, err := f.fsc.getResourceHeaders(f.buildPath(), compNone, resourceFile, http.MethodHead) + if err != nil { + return err + } + + f.updateEtagAndLastModified(headers) + f.updateProperties(headers) + f.Metadata = getMetadataFromHeaders(headers) + return nil +} + +// returns true if the range is larger than 4MB +func isRangeTooBig(fileRange FileRange) bool { + if fileRange.End-fileRange.Start > fourMB { + return true + } + + return false +} + +// ListRanges returns the list of valid ranges for this file. +// +// See https://msdn.microsoft.com/en-us/library/azure/dn166984.aspx +func (f *File) ListRanges(listRange *FileRange) (*FileRanges, error) { + params := url.Values{"comp": {"rangelist"}} + + // add optional range to list + var headers map[string]string + if listRange != nil { + headers = make(map[string]string) + headers["Range"] = listRange.String() + } + + resp, err := f.fsc.listContent(f.buildPath(), params, headers) + if err != nil { + return nil, err + } + + defer resp.body.Close() + var cl uint64 + cl, err = strconv.ParseUint(resp.headers.Get("x-ms-content-length"), 10, 64) + if err != nil { + return nil, err + } + + var out FileRanges + out.ContentLength = cl + out.ETag = resp.headers.Get("ETag") + out.LastModified = resp.headers.Get("Last-Modified") + + err = xmlUnmarshal(resp.body, &out) + return &out, err +} + +// modifies a range of bytes in this file +func (f *File) modifyRange(bytes io.Reader, fileRange FileRange, contentMD5 *string) (http.Header, error) { + if err := f.fsc.checkForStorageEmulator(); err != nil { + return nil, err + } + if fileRange.End < fileRange.Start { + return nil, errors.New("the value for rangeEnd must be greater than or equal to rangeStart") + } + if bytes != nil && isRangeTooBig(fileRange) { + return nil, errors.New("range cannot exceed 4MB in size") + } + + uri := f.fsc.client.getEndpoint(fileServiceName, f.buildPath(), url.Values{"comp": {"range"}}) + + // default to clear + write := "clear" + cl := uint64(0) + + // if bytes is not nil then this is an update operation + if bytes != nil { + write = "update" + cl = (fileRange.End - fileRange.Start) + 1 + } + + extraHeaders := map[string]string{ + "Content-Length": strconv.FormatUint(cl, 10), + "Range": fileRange.String(), + "x-ms-write": write, + } + + if contentMD5 != nil { + extraHeaders["Content-MD5"] = *contentMD5 + } + + headers := mergeHeaders(f.fsc.client.getStandardHeaders(), extraHeaders) + resp, err := f.fsc.client.exec(http.MethodPut, uri, headers, bytes, f.fsc.auth) + if err != nil { + return nil, err + } + defer resp.body.Close() + return resp.headers, checkRespCode(resp.statusCode, []int{http.StatusCreated}) +} + +// SetMetadata replaces the metadata for this file. +// +// Some keys may be converted to Camel-Case before sending. All keys +// are returned in lower case by GetFileMetadata. HTTP header names +// are case-insensitive so case munging should not matter to other +// applications either. +// +// See https://msdn.microsoft.com/en-us/library/azure/dn689097.aspx +func (f *File) SetMetadata() error { + headers, err := f.fsc.setResourceHeaders(f.buildPath(), compMetadata, resourceFile, mergeMDIntoExtraHeaders(f.Metadata, nil)) + if err != nil { + return err + } + + f.updateEtagAndLastModified(headers) + return nil +} + +// SetProperties sets system properties on this file. +// +// Some keys may be converted to Camel-Case before sending. All keys +// are returned in lower case by SetFileProperties. HTTP header names +// are case-insensitive so case munging should not matter to other +// applications either. +// +// See https://msdn.microsoft.com/en-us/library/azure/dn166975.aspx +func (f *File) SetProperties() error { + headers, err := f.fsc.setResourceHeaders(f.buildPath(), compProperties, resourceFile, headersFromStruct(f.Properties)) + if err != nil { + return err + } + + f.updateEtagAndLastModified(headers) + return nil +} + +// updates Etag and last modified date +func (f *File) updateEtagAndLastModified(headers http.Header) { + f.Properties.Etag = headers.Get("Etag") + f.Properties.LastModified = headers.Get("Last-Modified") +} + +// updates file properties from the specified HTTP header +func (f *File) updateProperties(header http.Header) { + size, err := strconv.ParseUint(header.Get("Content-Length"), 10, 64) + if err == nil { + f.Properties.Length = size + } + + f.updateEtagAndLastModified(header) + f.Properties.CacheControl = header.Get("Cache-Control") + f.Properties.Disposition = header.Get("Content-Disposition") + f.Properties.Encoding = header.Get("Content-Encoding") + f.Properties.Language = header.Get("Content-Language") + f.Properties.MD5 = header.Get("Content-MD5") + f.Properties.Type = header.Get("Content-Type") +} + +// URL gets the canonical URL to this file. +// This method does not create a publicly accessible URL if the file +// is private and this method does not check if the file exists. +func (f *File) URL() string { + return f.fsc.client.getEndpoint(fileServiceName, f.buildPath(), url.Values{}) +} + +// WriteRange writes a range of bytes to this file with an optional MD5 hash of the content. +// Note that the length of bytes must match (rangeEnd - rangeStart) + 1 with a maximum size of 4MB. +// +// See https://msdn.microsoft.com/en-us/library/azure/dn194276.aspx +func (f *File) WriteRange(bytes io.Reader, fileRange FileRange, contentMD5 *string) error { + if bytes == nil { + return errors.New("bytes cannot be nil") + } + + headers, err := f.modifyRange(bytes, fileRange, contentMD5) + if err != nil { + return err + } + + f.updateEtagAndLastModified(headers) + return nil +} diff --git a/vendor/github.com/Azure/azure-sdk-for-go/storage/fileserviceclient.go b/vendor/github.com/Azure/azure-sdk-for-go/storage/fileserviceclient.go new file mode 100644 index 000000000..81e094f00 --- /dev/null +++ b/vendor/github.com/Azure/azure-sdk-for-go/storage/fileserviceclient.go @@ -0,0 +1,360 @@ +package storage + +import ( + "encoding/xml" + "fmt" + "net/http" + "net/url" + "strings" +) + +// FileServiceClient contains operations for Microsoft Azure File Service. +type FileServiceClient struct { + client Client + auth authentication +} + +// ListSharesParameters defines the set of customizable parameters to make a +// List Shares call. +// +// See https://msdn.microsoft.com/en-us/library/azure/dn167009.aspx +type ListSharesParameters struct { + Prefix string + Marker string + Include string + MaxResults uint + Timeout uint +} + +// ShareListResponse contains the response fields from +// ListShares call. +// +// See https://msdn.microsoft.com/en-us/library/azure/dn167009.aspx +type ShareListResponse struct { + XMLName xml.Name `xml:"EnumerationResults"` + Xmlns string `xml:"xmlns,attr"` + Prefix string `xml:"Prefix"` + Marker string `xml:"Marker"` + NextMarker string `xml:"NextMarker"` + MaxResults int64 `xml:"MaxResults"` + Shares []Share `xml:"Shares>Share"` +} + +type compType string + +const ( + compNone compType = "" + compList compType = "list" + compMetadata compType = "metadata" + compProperties compType = "properties" + compRangeList compType = "rangelist" +) + +func (ct compType) String() string { + return string(ct) +} + +type resourceType string + +const ( + resourceDirectory resourceType = "directory" + resourceFile resourceType = "" + resourceShare resourceType = "share" +) + +func (rt resourceType) String() string { + return string(rt) +} + +func (p ListSharesParameters) getParameters() url.Values { + out := url.Values{} + + if p.Prefix != "" { + out.Set("prefix", p.Prefix) + } + if p.Marker != "" { + out.Set("marker", p.Marker) + } + if p.Include != "" { + out.Set("include", p.Include) + } + if p.MaxResults != 0 { + out.Set("maxresults", fmt.Sprintf("%v", p.MaxResults)) + } + if p.Timeout != 0 { + out.Set("timeout", fmt.Sprintf("%v", p.Timeout)) + } + + return out +} + +func (p ListDirsAndFilesParameters) getParameters() url.Values { + out := url.Values{} + + if p.Marker != "" { + out.Set("marker", p.Marker) + } + if p.MaxResults != 0 { + out.Set("maxresults", fmt.Sprintf("%v", p.MaxResults)) + } + if p.Timeout != 0 { + out.Set("timeout", fmt.Sprintf("%v", p.Timeout)) + } + + return out +} + +// returns url.Values for the specified types +func getURLInitValues(comp compType, res resourceType) url.Values { + values := url.Values{} + if comp != compNone { + values.Set("comp", comp.String()) + } + if res != resourceFile { + values.Set("restype", res.String()) + } + return values +} + +// GetShareReference returns a Share object for the specified share name. +func (f FileServiceClient) GetShareReference(name string) Share { + return Share{ + fsc: &f, + Name: name, + Properties: ShareProperties{ + Quota: -1, + }, + } +} + +// ListShares returns the list of shares in a storage account along with +// pagination token and other response details. +// +// See https://msdn.microsoft.com/en-us/library/azure/dd179352.aspx +func (f FileServiceClient) ListShares(params ListSharesParameters) (*ShareListResponse, error) { + q := mergeParams(params.getParameters(), url.Values{"comp": {"list"}}) + + var out ShareListResponse + resp, err := f.listContent("", q, nil) + if err != nil { + return nil, err + } + defer resp.body.Close() + err = xmlUnmarshal(resp.body, &out) + + // assign our client to the newly created Share objects + for i := range out.Shares { + out.Shares[i].fsc = &f + } + return &out, err +} + +// retrieves directory or share content +func (f FileServiceClient) listContent(path string, params url.Values, extraHeaders map[string]string) (*storageResponse, error) { + if err := f.checkForStorageEmulator(); err != nil { + return nil, err + } + + uri := f.client.getEndpoint(fileServiceName, path, params) + extraHeaders = f.client.protectUserAgent(extraHeaders) + headers := mergeHeaders(f.client.getStandardHeaders(), extraHeaders) + + resp, err := f.client.exec(http.MethodGet, uri, headers, nil, f.auth) + if err != nil { + return nil, err + } + + if err = checkRespCode(resp.statusCode, []int{http.StatusOK}); err != nil { + resp.body.Close() + return nil, err + } + + return resp, nil +} + +// returns true if the specified resource exists +func (f FileServiceClient) resourceExists(path string, res resourceType) (bool, http.Header, error) { + if err := f.checkForStorageEmulator(); err != nil { + return false, nil, err + } + + uri := f.client.getEndpoint(fileServiceName, path, getURLInitValues(compNone, res)) + headers := f.client.getStandardHeaders() + + resp, err := f.client.exec(http.MethodHead, uri, headers, nil, f.auth) + if resp != nil { + defer resp.body.Close() + if resp.statusCode == http.StatusOK || resp.statusCode == http.StatusNotFound { + return resp.statusCode == http.StatusOK, resp.headers, nil + } + } + return false, nil, err +} + +// creates a resource depending on the specified resource type +func (f FileServiceClient) createResource(path string, res resourceType, extraHeaders map[string]string) (http.Header, error) { + resp, err := f.createResourceNoClose(path, res, extraHeaders) + if err != nil { + return nil, err + } + defer resp.body.Close() + return resp.headers, checkRespCode(resp.statusCode, []int{http.StatusCreated}) +} + +// creates a resource depending on the specified resource type, doesn't close the response body +func (f FileServiceClient) createResourceNoClose(path string, res resourceType, extraHeaders map[string]string) (*storageResponse, error) { + if err := f.checkForStorageEmulator(); err != nil { + return nil, err + } + + values := getURLInitValues(compNone, res) + uri := f.client.getEndpoint(fileServiceName, path, values) + extraHeaders = f.client.protectUserAgent(extraHeaders) + headers := mergeHeaders(f.client.getStandardHeaders(), extraHeaders) + + return f.client.exec(http.MethodPut, uri, headers, nil, f.auth) +} + +// returns HTTP header data for the specified directory or share +func (f FileServiceClient) getResourceHeaders(path string, comp compType, res resourceType, verb string) (http.Header, error) { + resp, err := f.getResourceNoClose(path, comp, res, verb, nil) + if err != nil { + return nil, err + } + defer resp.body.Close() + + if err = checkRespCode(resp.statusCode, []int{http.StatusOK}); err != nil { + return nil, err + } + + return resp.headers, nil +} + +// gets the specified resource, doesn't close the response body +func (f FileServiceClient) getResourceNoClose(path string, comp compType, res resourceType, verb string, extraHeaders map[string]string) (*storageResponse, error) { + if err := f.checkForStorageEmulator(); err != nil { + return nil, err + } + + params := getURLInitValues(comp, res) + uri := f.client.getEndpoint(fileServiceName, path, params) + extraHeaders = f.client.protectUserAgent(extraHeaders) + headers := mergeHeaders(f.client.getStandardHeaders(), extraHeaders) + + return f.client.exec(verb, uri, headers, nil, f.auth) +} + +// deletes the resource and returns the response +func (f FileServiceClient) deleteResource(path string, res resourceType) error { + resp, err := f.deleteResourceNoClose(path, res) + if err != nil { + return err + } + defer resp.body.Close() + return checkRespCode(resp.statusCode, []int{http.StatusAccepted}) +} + +// deletes the resource and returns the response, doesn't close the response body +func (f FileServiceClient) deleteResourceNoClose(path string, res resourceType) (*storageResponse, error) { + if err := f.checkForStorageEmulator(); err != nil { + return nil, err + } + + values := getURLInitValues(compNone, res) + uri := f.client.getEndpoint(fileServiceName, path, values) + return f.client.exec(http.MethodDelete, uri, f.client.getStandardHeaders(), nil, f.auth) +} + +// merges metadata into extraHeaders and returns extraHeaders +func mergeMDIntoExtraHeaders(metadata, extraHeaders map[string]string) map[string]string { + if metadata == nil && extraHeaders == nil { + return nil + } + if extraHeaders == nil { + extraHeaders = make(map[string]string) + } + for k, v := range metadata { + extraHeaders[userDefinedMetadataHeaderPrefix+k] = v + } + return extraHeaders +} + +// merges extraHeaders into headers and returns headers +func mergeHeaders(headers, extraHeaders map[string]string) map[string]string { + for k, v := range extraHeaders { + headers[k] = v + } + return headers +} + +// sets extra header data for the specified resource +func (f FileServiceClient) setResourceHeaders(path string, comp compType, res resourceType, extraHeaders map[string]string) (http.Header, error) { + if err := f.checkForStorageEmulator(); err != nil { + return nil, err + } + + params := getURLInitValues(comp, res) + uri := f.client.getEndpoint(fileServiceName, path, params) + extraHeaders = f.client.protectUserAgent(extraHeaders) + headers := mergeHeaders(f.client.getStandardHeaders(), extraHeaders) + + resp, err := f.client.exec(http.MethodPut, uri, headers, nil, f.auth) + if err != nil { + return nil, err + } + defer resp.body.Close() + + return resp.headers, checkRespCode(resp.statusCode, []int{http.StatusOK}) +} + +// gets metadata for the specified resource +func (f FileServiceClient) getMetadata(path string, res resourceType) (map[string]string, error) { + if err := f.checkForStorageEmulator(); err != nil { + return nil, err + } + + headers, err := f.getResourceHeaders(path, compMetadata, res, http.MethodGet) + if err != nil { + return nil, err + } + + return getMetadataFromHeaders(headers), nil +} + +// returns a map of custom metadata values from the specified HTTP header +func getMetadataFromHeaders(header http.Header) map[string]string { + metadata := make(map[string]string) + for k, v := range header { + // Can't trust CanonicalHeaderKey() to munge case + // reliably. "_" is allowed in identifiers: + // https://msdn.microsoft.com/en-us/library/azure/dd179414.aspx + // https://msdn.microsoft.com/library/aa664670(VS.71).aspx + // http://tools.ietf.org/html/rfc7230#section-3.2 + // ...but "_" is considered invalid by + // CanonicalMIMEHeaderKey in + // https://golang.org/src/net/textproto/reader.go?s=14615:14659#L542 + // so k can be "X-Ms-Meta-Foo" or "x-ms-meta-foo_bar". + k = strings.ToLower(k) + if len(v) == 0 || !strings.HasPrefix(k, strings.ToLower(userDefinedMetadataHeaderPrefix)) { + continue + } + // metadata["foo"] = content of the last X-Ms-Meta-Foo header + k = k[len(userDefinedMetadataHeaderPrefix):] + metadata[k] = v[len(v)-1] + } + + if len(metadata) == 0 { + return nil + } + + return metadata +} + +//checkForStorageEmulator determines if the client is setup for use with +//Azure Storage Emulator, and returns a relevant error +func (f FileServiceClient) checkForStorageEmulator() error { + if f.client.accountName == StorageEmulatorAccountName { + return fmt.Errorf("Error: File service is not currently supported by Azure Storage Emulator") + } + return nil +} diff --git a/vendor/github.com/Azure/azure-sdk-for-go/storage/queue.go b/vendor/github.com/Azure/azure-sdk-for-go/storage/queue.go new file mode 100644 index 000000000..4dbb67733 --- /dev/null +++ b/vendor/github.com/Azure/azure-sdk-for-go/storage/queue.go @@ -0,0 +1,346 @@ +package storage + +import ( + "encoding/xml" + "fmt" + "net/http" + "net/url" + "strconv" + "strings" +) + +const ( + // casing is per Golang's http.Header canonicalizing the header names. + approximateMessagesCountHeader = "X-Ms-Approximate-Messages-Count" + userDefinedMetadataHeaderPrefix = "X-Ms-Meta-" +) + +// QueueServiceClient contains operations for Microsoft Azure Queue Storage +// Service. +type QueueServiceClient struct { + client Client + auth authentication +} + +func pathForQueue(queue string) string { return fmt.Sprintf("/%s", queue) } +func pathForQueueMessages(queue string) string { return fmt.Sprintf("/%s/messages", queue) } +func pathForMessage(queue, name string) string { return fmt.Sprintf("/%s/messages/%s", queue, name) } + +type putMessageRequest struct { + XMLName xml.Name `xml:"QueueMessage"` + MessageText string `xml:"MessageText"` +} + +// PutMessageParameters is the set of options can be specified for Put Messsage +// operation. A zero struct does not use any preferences for the request. +type PutMessageParameters struct { + VisibilityTimeout int + MessageTTL int +} + +func (p PutMessageParameters) getParameters() url.Values { + out := url.Values{} + if p.VisibilityTimeout != 0 { + out.Set("visibilitytimeout", strconv.Itoa(p.VisibilityTimeout)) + } + if p.MessageTTL != 0 { + out.Set("messagettl", strconv.Itoa(p.MessageTTL)) + } + return out +} + +// GetMessagesParameters is the set of options can be specified for Get +// Messsages operation. A zero struct does not use any preferences for the +// request. +type GetMessagesParameters struct { + NumOfMessages int + VisibilityTimeout int +} + +func (p GetMessagesParameters) getParameters() url.Values { + out := url.Values{} + if p.NumOfMessages != 0 { + out.Set("numofmessages", strconv.Itoa(p.NumOfMessages)) + } + if p.VisibilityTimeout != 0 { + out.Set("visibilitytimeout", strconv.Itoa(p.VisibilityTimeout)) + } + return out +} + +// PeekMessagesParameters is the set of options can be specified for Peek +// Messsage operation. A zero struct does not use any preferences for the +// request. +type PeekMessagesParameters struct { + NumOfMessages int +} + +func (p PeekMessagesParameters) getParameters() url.Values { + out := url.Values{"peekonly": {"true"}} // Required for peek operation + if p.NumOfMessages != 0 { + out.Set("numofmessages", strconv.Itoa(p.NumOfMessages)) + } + return out +} + +// UpdateMessageParameters is the set of options can be specified for Update Messsage +// operation. A zero struct does not use any preferences for the request. +type UpdateMessageParameters struct { + PopReceipt string + VisibilityTimeout int +} + +func (p UpdateMessageParameters) getParameters() url.Values { + out := url.Values{} + if p.PopReceipt != "" { + out.Set("popreceipt", p.PopReceipt) + } + if p.VisibilityTimeout != 0 { + out.Set("visibilitytimeout", strconv.Itoa(p.VisibilityTimeout)) + } + return out +} + +// GetMessagesResponse represents a response returned from Get Messages +// operation. +type GetMessagesResponse struct { + XMLName xml.Name `xml:"QueueMessagesList"` + QueueMessagesList []GetMessageResponse `xml:"QueueMessage"` +} + +// GetMessageResponse represents a QueueMessage object returned from Get +// Messages operation response. +type GetMessageResponse struct { + MessageID string `xml:"MessageId"` + InsertionTime string `xml:"InsertionTime"` + ExpirationTime string `xml:"ExpirationTime"` + PopReceipt string `xml:"PopReceipt"` + TimeNextVisible string `xml:"TimeNextVisible"` + DequeueCount int `xml:"DequeueCount"` + MessageText string `xml:"MessageText"` +} + +// PeekMessagesResponse represents a response returned from Get Messages +// operation. +type PeekMessagesResponse struct { + XMLName xml.Name `xml:"QueueMessagesList"` + QueueMessagesList []PeekMessageResponse `xml:"QueueMessage"` +} + +// PeekMessageResponse represents a QueueMessage object returned from Peek +// Messages operation response. +type PeekMessageResponse struct { + MessageID string `xml:"MessageId"` + InsertionTime string `xml:"InsertionTime"` + ExpirationTime string `xml:"ExpirationTime"` + DequeueCount int `xml:"DequeueCount"` + MessageText string `xml:"MessageText"` +} + +// QueueMetadataResponse represents user defined metadata and queue +// properties on a specific queue. +// +// See https://msdn.microsoft.com/en-us/library/azure/dd179384.aspx +type QueueMetadataResponse struct { + ApproximateMessageCount int + UserDefinedMetadata map[string]string +} + +// SetMetadata operation sets user-defined metadata on the specified queue. +// Metadata is associated with the queue as name-value pairs. +// +// See https://msdn.microsoft.com/en-us/library/azure/dd179348.aspx +func (c QueueServiceClient) SetMetadata(name string, metadata map[string]string) error { + uri := c.client.getEndpoint(queueServiceName, pathForQueue(name), url.Values{"comp": []string{"metadata"}}) + metadata = c.client.protectUserAgent(metadata) + headers := c.client.getStandardHeaders() + for k, v := range metadata { + headers[userDefinedMetadataHeaderPrefix+k] = v + } + + resp, err := c.client.exec(http.MethodPut, uri, headers, nil, c.auth) + if err != nil { + return err + } + defer resp.body.Close() + + return checkRespCode(resp.statusCode, []int{http.StatusNoContent}) +} + +// GetMetadata operation retrieves user-defined metadata and queue +// properties on the specified queue. Metadata is associated with +// the queue as name-values pairs. +// +// See https://msdn.microsoft.com/en-us/library/azure/dd179384.aspx +// +// Because the way Golang's http client (and http.Header in particular) +// canonicalize header names, the returned metadata names would always +// be all lower case. +func (c QueueServiceClient) GetMetadata(name string) (QueueMetadataResponse, error) { + qm := QueueMetadataResponse{} + qm.UserDefinedMetadata = make(map[string]string) + uri := c.client.getEndpoint(queueServiceName, pathForQueue(name), url.Values{"comp": []string{"metadata"}}) + headers := c.client.getStandardHeaders() + resp, err := c.client.exec(http.MethodGet, uri, headers, nil, c.auth) + if err != nil { + return qm, err + } + defer resp.body.Close() + + for k, v := range resp.headers { + if len(v) != 1 { + return qm, fmt.Errorf("Unexpected number of values (%d) in response header '%s'", len(v), k) + } + + value := v[0] + + if k == approximateMessagesCountHeader { + qm.ApproximateMessageCount, err = strconv.Atoi(value) + if err != nil { + return qm, fmt.Errorf("Unexpected value in response header '%s': '%s' ", k, value) + } + } else if strings.HasPrefix(k, userDefinedMetadataHeaderPrefix) { + name := strings.TrimPrefix(k, userDefinedMetadataHeaderPrefix) + qm.UserDefinedMetadata[strings.ToLower(name)] = value + } + } + + return qm, checkRespCode(resp.statusCode, []int{http.StatusOK}) +} + +// CreateQueue operation creates a queue under the given account. +// +// See https://msdn.microsoft.com/en-us/library/azure/dd179342.aspx +func (c QueueServiceClient) CreateQueue(name string) error { + uri := c.client.getEndpoint(queueServiceName, pathForQueue(name), url.Values{}) + headers := c.client.getStandardHeaders() + resp, err := c.client.exec(http.MethodPut, uri, headers, nil, c.auth) + if err != nil { + return err + } + defer resp.body.Close() + return checkRespCode(resp.statusCode, []int{http.StatusCreated}) +} + +// DeleteQueue operation permanently deletes the specified queue. +// +// See https://msdn.microsoft.com/en-us/library/azure/dd179436.aspx +func (c QueueServiceClient) DeleteQueue(name string) error { + uri := c.client.getEndpoint(queueServiceName, pathForQueue(name), url.Values{}) + resp, err := c.client.exec(http.MethodDelete, uri, c.client.getStandardHeaders(), nil, c.auth) + if err != nil { + return err + } + defer resp.body.Close() + return checkRespCode(resp.statusCode, []int{http.StatusNoContent}) +} + +// QueueExists returns true if a queue with given name exists. +func (c QueueServiceClient) QueueExists(name string) (bool, error) { + uri := c.client.getEndpoint(queueServiceName, pathForQueue(name), url.Values{"comp": {"metadata"}}) + resp, err := c.client.exec(http.MethodGet, uri, c.client.getStandardHeaders(), nil, c.auth) + if resp != nil && (resp.statusCode == http.StatusOK || resp.statusCode == http.StatusNotFound) { + return resp.statusCode == http.StatusOK, nil + } + + return false, err +} + +// PutMessage operation adds a new message to the back of the message queue. +// +// See https://msdn.microsoft.com/en-us/library/azure/dd179346.aspx +func (c QueueServiceClient) PutMessage(queue string, message string, params PutMessageParameters) error { + uri := c.client.getEndpoint(queueServiceName, pathForQueueMessages(queue), params.getParameters()) + req := putMessageRequest{MessageText: message} + body, nn, err := xmlMarshal(req) + if err != nil { + return err + } + headers := c.client.getStandardHeaders() + headers["Content-Length"] = strconv.Itoa(nn) + resp, err := c.client.exec(http.MethodPost, uri, headers, body, c.auth) + if err != nil { + return err + } + defer resp.body.Close() + return checkRespCode(resp.statusCode, []int{http.StatusCreated}) +} + +// ClearMessages operation deletes all messages from the specified queue. +// +// See https://msdn.microsoft.com/en-us/library/azure/dd179454.aspx +func (c QueueServiceClient) ClearMessages(queue string) error { + uri := c.client.getEndpoint(queueServiceName, pathForQueueMessages(queue), url.Values{}) + resp, err := c.client.exec(http.MethodDelete, uri, c.client.getStandardHeaders(), nil, c.auth) + if err != nil { + return err + } + defer resp.body.Close() + return checkRespCode(resp.statusCode, []int{http.StatusNoContent}) +} + +// GetMessages operation retrieves one or more messages from the front of the +// queue. +// +// See https://msdn.microsoft.com/en-us/library/azure/dd179474.aspx +func (c QueueServiceClient) GetMessages(queue string, params GetMessagesParameters) (GetMessagesResponse, error) { + var r GetMessagesResponse + uri := c.client.getEndpoint(queueServiceName, pathForQueueMessages(queue), params.getParameters()) + resp, err := c.client.exec(http.MethodGet, uri, c.client.getStandardHeaders(), nil, c.auth) + if err != nil { + return r, err + } + defer resp.body.Close() + err = xmlUnmarshal(resp.body, &r) + return r, err +} + +// PeekMessages retrieves one or more messages from the front of the queue, but +// does not alter the visibility of the message. +// +// See https://msdn.microsoft.com/en-us/library/azure/dd179472.aspx +func (c QueueServiceClient) PeekMessages(queue string, params PeekMessagesParameters) (PeekMessagesResponse, error) { + var r PeekMessagesResponse + uri := c.client.getEndpoint(queueServiceName, pathForQueueMessages(queue), params.getParameters()) + resp, err := c.client.exec(http.MethodGet, uri, c.client.getStandardHeaders(), nil, c.auth) + if err != nil { + return r, err + } + defer resp.body.Close() + err = xmlUnmarshal(resp.body, &r) + return r, err +} + +// DeleteMessage operation deletes the specified message. +// +// See https://msdn.microsoft.com/en-us/library/azure/dd179347.aspx +func (c QueueServiceClient) DeleteMessage(queue, messageID, popReceipt string) error { + uri := c.client.getEndpoint(queueServiceName, pathForMessage(queue, messageID), url.Values{ + "popreceipt": {popReceipt}}) + resp, err := c.client.exec(http.MethodDelete, uri, c.client.getStandardHeaders(), nil, c.auth) + if err != nil { + return err + } + defer resp.body.Close() + return checkRespCode(resp.statusCode, []int{http.StatusNoContent}) +} + +// UpdateMessage operation deletes the specified message. +// +// See https://msdn.microsoft.com/en-us/library/azure/hh452234.aspx +func (c QueueServiceClient) UpdateMessage(queue string, messageID string, message string, params UpdateMessageParameters) error { + uri := c.client.getEndpoint(queueServiceName, pathForMessage(queue, messageID), params.getParameters()) + req := putMessageRequest{MessageText: message} + body, nn, err := xmlMarshal(req) + if err != nil { + return err + } + headers := c.client.getStandardHeaders() + headers["Content-Length"] = fmt.Sprintf("%d", nn) + resp, err := c.client.exec(http.MethodPut, uri, headers, body, c.auth) + if err != nil { + return err + } + defer resp.body.Close() + return checkRespCode(resp.statusCode, []int{http.StatusNoContent}) +} diff --git a/vendor/github.com/Azure/azure-sdk-for-go/storage/share.go b/vendor/github.com/Azure/azure-sdk-for-go/storage/share.go new file mode 100644 index 000000000..8423aedcc --- /dev/null +++ b/vendor/github.com/Azure/azure-sdk-for-go/storage/share.go @@ -0,0 +1,186 @@ +package storage + +import ( + "fmt" + "net/http" + "net/url" + "strconv" +) + +// Share represents an Azure file share. +type Share struct { + fsc *FileServiceClient + Name string `xml:"Name"` + Properties ShareProperties `xml:"Properties"` + Metadata map[string]string +} + +// ShareProperties contains various properties of a share. +type ShareProperties struct { + LastModified string `xml:"Last-Modified"` + Etag string `xml:"Etag"` + Quota int `xml:"Quota"` +} + +// builds the complete path for this share object. +func (s *Share) buildPath() string { + return fmt.Sprintf("/%s", s.Name) +} + +// Create this share under the associated account. +// If a share with the same name already exists, the operation fails. +// +// See https://msdn.microsoft.com/en-us/library/azure/dn167008.aspx +func (s *Share) Create() error { + headers, err := s.fsc.createResource(s.buildPath(), resourceShare, mergeMDIntoExtraHeaders(s.Metadata, nil)) + if err != nil { + return err + } + + s.updateEtagAndLastModified(headers) + return nil +} + +// CreateIfNotExists creates this share under the associated account if +// it does not exist. Returns true if the share is newly created or false if +// the share already exists. +// +// See https://msdn.microsoft.com/en-us/library/azure/dn167008.aspx +func (s *Share) CreateIfNotExists() (bool, error) { + resp, err := s.fsc.createResourceNoClose(s.buildPath(), resourceShare, nil) + if resp != nil { + defer resp.body.Close() + if resp.statusCode == http.StatusCreated || resp.statusCode == http.StatusConflict { + if resp.statusCode == http.StatusCreated { + s.updateEtagAndLastModified(resp.headers) + return true, nil + } + return false, s.FetchAttributes() + } + } + + return false, err +} + +// Delete marks this share for deletion. The share along with any files +// and directories contained within it are later deleted during garbage +// collection. If the share does not exist the operation fails +// +// See https://msdn.microsoft.com/en-us/library/azure/dn689090.aspx +func (s *Share) Delete() error { + return s.fsc.deleteResource(s.buildPath(), resourceShare) +} + +// DeleteIfExists operation marks this share for deletion if it exists. +// +// See https://msdn.microsoft.com/en-us/library/azure/dn689090.aspx +func (s *Share) DeleteIfExists() (bool, error) { + resp, err := s.fsc.deleteResourceNoClose(s.buildPath(), resourceShare) + if resp != nil { + defer resp.body.Close() + if resp.statusCode == http.StatusAccepted || resp.statusCode == http.StatusNotFound { + return resp.statusCode == http.StatusAccepted, nil + } + } + return false, err +} + +// Exists returns true if this share already exists +// on the storage account, otherwise returns false. +func (s *Share) Exists() (bool, error) { + exists, headers, err := s.fsc.resourceExists(s.buildPath(), resourceShare) + if exists { + s.updateEtagAndLastModified(headers) + s.updateQuota(headers) + } + return exists, err +} + +// FetchAttributes retrieves metadata and properties for this share. +func (s *Share) FetchAttributes() error { + headers, err := s.fsc.getResourceHeaders(s.buildPath(), compNone, resourceShare, http.MethodHead) + if err != nil { + return err + } + + s.updateEtagAndLastModified(headers) + s.updateQuota(headers) + s.Metadata = getMetadataFromHeaders(headers) + + return nil +} + +// GetRootDirectoryReference returns a Directory object at the root of this share. +func (s *Share) GetRootDirectoryReference() *Directory { + return &Directory{ + fsc: s.fsc, + share: s, + } +} + +// ServiceClient returns the FileServiceClient associated with this share. +func (s *Share) ServiceClient() *FileServiceClient { + return s.fsc +} + +// SetMetadata replaces the metadata for this share. +// +// Some keys may be converted to Camel-Case before sending. All keys +// are returned in lower case by GetShareMetadata. HTTP header names +// are case-insensitive so case munging should not matter to other +// applications either. +// +// See https://msdn.microsoft.com/en-us/library/azure/dd179414.aspx +func (s *Share) SetMetadata() error { + headers, err := s.fsc.setResourceHeaders(s.buildPath(), compMetadata, resourceShare, mergeMDIntoExtraHeaders(s.Metadata, nil)) + if err != nil { + return err + } + + s.updateEtagAndLastModified(headers) + return nil +} + +// SetProperties sets system properties for this share. +// +// Some keys may be converted to Camel-Case before sending. All keys +// are returned in lower case by SetShareProperties. HTTP header names +// are case-insensitive so case munging should not matter to other +// applications either. +// +// See https://msdn.microsoft.com/en-us/library/azure/mt427368.aspx +func (s *Share) SetProperties() error { + if s.Properties.Quota < 1 || s.Properties.Quota > 5120 { + return fmt.Errorf("invalid value %v for quota, valid values are [1, 5120]", s.Properties.Quota) + } + + headers, err := s.fsc.setResourceHeaders(s.buildPath(), compProperties, resourceShare, map[string]string{ + "x-ms-share-quota": strconv.Itoa(s.Properties.Quota), + }) + if err != nil { + return err + } + + s.updateEtagAndLastModified(headers) + return nil +} + +// updates Etag and last modified date +func (s *Share) updateEtagAndLastModified(headers http.Header) { + s.Properties.Etag = headers.Get("Etag") + s.Properties.LastModified = headers.Get("Last-Modified") +} + +// updates quota value +func (s *Share) updateQuota(headers http.Header) { + quota, err := strconv.Atoi(headers.Get("x-ms-share-quota")) + if err == nil { + s.Properties.Quota = quota + } +} + +// URL gets the canonical URL to this share. This method does not create a publicly accessible +// URL if the share is private and this method does not check if the share exists. +func (s *Share) URL() string { + return s.fsc.client.getEndpoint(fileServiceName, s.buildPath(), url.Values{}) +} diff --git a/vendor/github.com/Azure/azure-sdk-for-go/storage/storagepolicy.go b/vendor/github.com/Azure/azure-sdk-for-go/storage/storagepolicy.go new file mode 100644 index 000000000..bee1c31ad --- /dev/null +++ b/vendor/github.com/Azure/azure-sdk-for-go/storage/storagepolicy.go @@ -0,0 +1,47 @@ +package storage + +import ( + "strings" + "time" +) + +// AccessPolicyDetailsXML has specifics about an access policy +// annotated with XML details. +type AccessPolicyDetailsXML struct { + StartTime time.Time `xml:"Start"` + ExpiryTime time.Time `xml:"Expiry"` + Permission string `xml:"Permission"` +} + +// SignedIdentifier is a wrapper for a specific policy +type SignedIdentifier struct { + ID string `xml:"Id"` + AccessPolicy AccessPolicyDetailsXML `xml:"AccessPolicy"` +} + +// SignedIdentifiers part of the response from GetPermissions call. +type SignedIdentifiers struct { + SignedIdentifiers []SignedIdentifier `xml:"SignedIdentifier"` +} + +// AccessPolicy is the response type from the GetPermissions call. +type AccessPolicy struct { + SignedIdentifiersList SignedIdentifiers `xml:"SignedIdentifiers"` +} + +// convertAccessPolicyToXMLStructs converts between AccessPolicyDetails which is a struct better for API usage to the +// AccessPolicy struct which will get converted to XML. +func convertAccessPolicyToXMLStructs(id string, startTime time.Time, expiryTime time.Time, permissions string) SignedIdentifier { + return SignedIdentifier{ + ID: id, + AccessPolicy: AccessPolicyDetailsXML{ + StartTime: startTime.UTC().Round(time.Second), + ExpiryTime: expiryTime.UTC().Round(time.Second), + Permission: permissions, + }, + } +} + +func updatePermissions(permissions, permission string) bool { + return strings.Contains(permissions, permission) +} diff --git a/vendor/github.com/Azure/azure-sdk-for-go/storage/table.go b/vendor/github.com/Azure/azure-sdk-for-go/storage/table.go new file mode 100644 index 000000000..5cbc13ff1 --- /dev/null +++ b/vendor/github.com/Azure/azure-sdk-for-go/storage/table.go @@ -0,0 +1,258 @@ +package storage + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "strconv" + "time" +) + +// TableServiceClient contains operations for Microsoft Azure Table Storage +// Service. +type TableServiceClient struct { + client Client + auth authentication +} + +// AzureTable is the typedef of the Azure Table name +type AzureTable string + +const ( + tablesURIPath = "/Tables" +) + +type createTableRequest struct { + TableName string `json:"TableName"` +} + +// TableAccessPolicy are used for SETTING table policies +type TableAccessPolicy struct { + ID string + StartTime time.Time + ExpiryTime time.Time + CanRead bool + CanAppend bool + CanUpdate bool + CanDelete bool +} + +func pathForTable(table AzureTable) string { return fmt.Sprintf("%s", table) } + +func (c *TableServiceClient) getStandardHeaders() map[string]string { + return map[string]string{ + "x-ms-version": "2015-02-21", + "x-ms-date": currentTimeRfc1123Formatted(), + "Accept": "application/json;odata=nometadata", + "Accept-Charset": "UTF-8", + "Content-Type": "application/json", + userAgentHeader: c.client.userAgent, + } +} + +// QueryTables returns the tables created in the +// *TableServiceClient storage account. +func (c *TableServiceClient) QueryTables() ([]AzureTable, error) { + uri := c.client.getEndpoint(tableServiceName, tablesURIPath, url.Values{}) + + headers := c.getStandardHeaders() + headers["Content-Length"] = "0" + + resp, err := c.client.execInternalJSON(http.MethodGet, uri, headers, nil, c.auth) + if err != nil { + return nil, err + } + defer resp.body.Close() + + if err := checkRespCode(resp.statusCode, []int{http.StatusOK}); err != nil { + return nil, err + } + + buf := new(bytes.Buffer) + if _, err := buf.ReadFrom(resp.body); err != nil { + return nil, err + } + + var respArray queryTablesResponse + if err := json.Unmarshal(buf.Bytes(), &respArray); err != nil { + return nil, err + } + + s := make([]AzureTable, len(respArray.TableName)) + for i, elem := range respArray.TableName { + s[i] = AzureTable(elem.TableName) + } + + return s, nil +} + +// CreateTable creates the table given the specific +// name. This function fails if the name is not compliant +// with the specification or the tables already exists. +func (c *TableServiceClient) CreateTable(table AzureTable) error { + uri := c.client.getEndpoint(tableServiceName, tablesURIPath, url.Values{}) + + headers := c.getStandardHeaders() + + req := createTableRequest{TableName: string(table)} + buf := new(bytes.Buffer) + + if err := json.NewEncoder(buf).Encode(req); err != nil { + return err + } + + headers["Content-Length"] = fmt.Sprintf("%d", buf.Len()) + + resp, err := c.client.execInternalJSON(http.MethodPost, uri, headers, buf, c.auth) + + if err != nil { + return err + } + defer resp.body.Close() + + if err := checkRespCode(resp.statusCode, []int{http.StatusCreated}); err != nil { + return err + } + + return nil +} + +// DeleteTable deletes the table given the specific +// name. This function fails if the table is not present. +// Be advised: DeleteTable deletes all the entries +// that may be present. +func (c *TableServiceClient) DeleteTable(table AzureTable) error { + uri := c.client.getEndpoint(tableServiceName, tablesURIPath, url.Values{}) + uri += fmt.Sprintf("('%s')", string(table)) + + headers := c.getStandardHeaders() + + headers["Content-Length"] = "0" + + resp, err := c.client.execInternalJSON(http.MethodDelete, uri, headers, nil, c.auth) + + if err != nil { + return err + } + defer resp.body.Close() + + if err := checkRespCode(resp.statusCode, []int{http.StatusNoContent}); err != nil { + return err + + } + return nil +} + +// SetTablePermissions sets up table ACL permissions as per REST details https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Set-Table-ACL +func (c *TableServiceClient) SetTablePermissions(table AzureTable, policies []TableAccessPolicy, timeout uint) (err error) { + params := url.Values{"comp": {"acl"}} + + if timeout > 0 { + params.Add("timeout", fmt.Sprint(timeout)) + } + + uri := c.client.getEndpoint(tableServiceName, string(table), params) + headers := c.client.getStandardHeaders() + + body, length, err := generateTableACLPayload(policies) + if err != nil { + return err + } + headers["Content-Length"] = fmt.Sprintf("%v", length) + + resp, err := c.client.execInternalJSON(http.MethodPut, uri, headers, body, c.auth) + if err != nil { + return err + } + defer resp.body.Close() + + if err := checkRespCode(resp.statusCode, []int{http.StatusNoContent}); err != nil { + return err + } + return nil +} + +func generateTableACLPayload(policies []TableAccessPolicy) (io.Reader, int, error) { + sil := SignedIdentifiers{ + SignedIdentifiers: []SignedIdentifier{}, + } + for _, tap := range policies { + permission := generateTablePermissions(&tap) + signedIdentifier := convertAccessPolicyToXMLStructs(tap.ID, tap.StartTime, tap.ExpiryTime, permission) + sil.SignedIdentifiers = append(sil.SignedIdentifiers, signedIdentifier) + } + return xmlMarshal(sil) +} + +// GetTablePermissions gets the table ACL permissions, as per REST details https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/get-table-acl +func (c *TableServiceClient) GetTablePermissions(table AzureTable, timeout int) (permissionResponse []TableAccessPolicy, err error) { + params := url.Values{"comp": {"acl"}} + + if timeout > 0 { + params.Add("timeout", strconv.Itoa(timeout)) + } + + uri := c.client.getEndpoint(tableServiceName, string(table), params) + headers := c.client.getStandardHeaders() + resp, err := c.client.execInternalJSON(http.MethodGet, uri, headers, nil, c.auth) + if err != nil { + return nil, err + } + defer resp.body.Close() + + if err = checkRespCode(resp.statusCode, []int{http.StatusOK}); err != nil { + return nil, err + } + + var ap AccessPolicy + err = xmlUnmarshal(resp.body, &ap.SignedIdentifiersList) + if err != nil { + return nil, err + } + out := updateTableAccessPolicy(ap) + return out, nil +} + +func updateTableAccessPolicy(ap AccessPolicy) []TableAccessPolicy { + out := []TableAccessPolicy{} + for _, policy := range ap.SignedIdentifiersList.SignedIdentifiers { + tap := TableAccessPolicy{ + ID: policy.ID, + StartTime: policy.AccessPolicy.StartTime, + ExpiryTime: policy.AccessPolicy.ExpiryTime, + } + tap.CanRead = updatePermissions(policy.AccessPolicy.Permission, "r") + tap.CanAppend = updatePermissions(policy.AccessPolicy.Permission, "a") + tap.CanUpdate = updatePermissions(policy.AccessPolicy.Permission, "u") + tap.CanDelete = updatePermissions(policy.AccessPolicy.Permission, "d") + + out = append(out, tap) + } + return out +} + +func generateTablePermissions(tap *TableAccessPolicy) (permissions string) { + // generate the permissions string (raud). + // still want the end user API to have bool flags. + permissions = "" + + if tap.CanRead { + permissions += "r" + } + + if tap.CanAppend { + permissions += "a" + } + + if tap.CanUpdate { + permissions += "u" + } + + if tap.CanDelete { + permissions += "d" + } + return permissions +} diff --git a/vendor/github.com/Azure/azure-sdk-for-go/storage/table_entities.go b/vendor/github.com/Azure/azure-sdk-for-go/storage/table_entities.go new file mode 100644 index 000000000..1758d9f3e --- /dev/null +++ b/vendor/github.com/Azure/azure-sdk-for-go/storage/table_entities.go @@ -0,0 +1,345 @@ +package storage + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "reflect" +) + +// Annotating as secure for gas scanning +/* #nosec */ +const ( + partitionKeyNode = "PartitionKey" + rowKeyNode = "RowKey" + tag = "table" + tagIgnore = "-" + continuationTokenPartitionKeyHeader = "X-Ms-Continuation-Nextpartitionkey" + continuationTokenRowHeader = "X-Ms-Continuation-Nextrowkey" + maxTopParameter = 1000 +) + +type queryTablesResponse struct { + TableName []struct { + TableName string `json:"TableName"` + } `json:"value"` +} + +const ( + tableOperationTypeInsert = iota + tableOperationTypeUpdate = iota + tableOperationTypeMerge = iota + tableOperationTypeInsertOrReplace = iota + tableOperationTypeInsertOrMerge = iota +) + +type tableOperation int + +// TableEntity interface specifies +// the functions needed to support +// marshaling and unmarshaling into +// Azure Tables. The struct must only contain +// simple types because Azure Tables do not +// support hierarchy. +type TableEntity interface { + PartitionKey() string + RowKey() string + SetPartitionKey(string) error + SetRowKey(string) error +} + +// ContinuationToken is an opaque (ie not useful to inspect) +// struct that Get... methods can return if there are more +// entries to be returned than the ones already +// returned. Just pass it to the same function to continue +// receiving the remaining entries. +type ContinuationToken struct { + NextPartitionKey string + NextRowKey string +} + +type getTableEntriesResponse struct { + Elements []map[string]interface{} `json:"value"` +} + +// QueryTableEntities queries the specified table and returns the unmarshaled +// entities of type retType. +// top parameter limits the returned entries up to top. Maximum top +// allowed by Azure API is 1000. In case there are more than top entries to be +// returned the function will return a non nil *ContinuationToken. You can call the +// same function again passing the received ContinuationToken as previousContToken +// parameter in order to get the following entries. The query parameter +// is the odata query. To retrieve all the entries pass the empty string. +// The function returns a pointer to a TableEntity slice, the *ContinuationToken +// if there are more entries to be returned and an error in case something went +// wrong. +// +// Example: +// entities, cToken, err = tSvc.QueryTableEntities("table", cToken, reflect.TypeOf(entity), 20, "") +func (c *TableServiceClient) QueryTableEntities(tableName AzureTable, previousContToken *ContinuationToken, retType reflect.Type, top int, query string) ([]TableEntity, *ContinuationToken, error) { + if top > maxTopParameter { + return nil, nil, fmt.Errorf("top accepts at maximum %d elements. Requested %d instead", maxTopParameter, top) + } + + uri := c.client.getEndpoint(tableServiceName, pathForTable(tableName), url.Values{}) + uri += fmt.Sprintf("?$top=%d", top) + if query != "" { + uri += fmt.Sprintf("&$filter=%s", url.QueryEscape(query)) + } + + if previousContToken != nil { + uri += fmt.Sprintf("&NextPartitionKey=%s&NextRowKey=%s", previousContToken.NextPartitionKey, previousContToken.NextRowKey) + } + + headers := c.getStandardHeaders() + + headers["Content-Length"] = "0" + + resp, err := c.client.execInternalJSON(http.MethodGet, uri, headers, nil, c.auth) + + if err != nil { + return nil, nil, err + } + + contToken := extractContinuationTokenFromHeaders(resp.headers) + + defer resp.body.Close() + + if err = checkRespCode(resp.statusCode, []int{http.StatusOK}); err != nil { + return nil, contToken, err + } + + retEntries, err := deserializeEntity(retType, resp.body) + if err != nil { + return nil, contToken, err + } + + return retEntries, contToken, nil +} + +// InsertEntity inserts an entity in the specified table. +// The function fails if there is an entity with the same +// PartitionKey and RowKey in the table. +func (c *TableServiceClient) InsertEntity(table AzureTable, entity TableEntity) error { + if sc, err := c.execTable(table, entity, false, http.MethodPost); err != nil { + return checkRespCode(sc, []int{http.StatusCreated}) + } + + return nil +} + +func (c *TableServiceClient) execTable(table AzureTable, entity TableEntity, specifyKeysInURL bool, method string) (int, error) { + uri := c.client.getEndpoint(tableServiceName, pathForTable(table), url.Values{}) + if specifyKeysInURL { + uri += fmt.Sprintf("(PartitionKey='%s',RowKey='%s')", url.QueryEscape(entity.PartitionKey()), url.QueryEscape(entity.RowKey())) + } + + headers := c.getStandardHeaders() + + var buf bytes.Buffer + + if err := injectPartitionAndRowKeys(entity, &buf); err != nil { + return 0, err + } + + headers["Content-Length"] = fmt.Sprintf("%d", buf.Len()) + + resp, err := c.client.execInternalJSON(method, uri, headers, &buf, c.auth) + + if err != nil { + return 0, err + } + + defer resp.body.Close() + + return resp.statusCode, nil +} + +// UpdateEntity updates the contents of an entity with the +// one passed as parameter. The function fails if there is no entity +// with the same PartitionKey and RowKey in the table. +func (c *TableServiceClient) UpdateEntity(table AzureTable, entity TableEntity) error { + if sc, err := c.execTable(table, entity, true, http.MethodPut); err != nil { + return checkRespCode(sc, []int{http.StatusNoContent}) + } + return nil +} + +// MergeEntity merges the contents of an entity with the +// one passed as parameter. +// The function fails if there is no entity +// with the same PartitionKey and RowKey in the table. +func (c *TableServiceClient) MergeEntity(table AzureTable, entity TableEntity) error { + if sc, err := c.execTable(table, entity, true, "MERGE"); err != nil { + return checkRespCode(sc, []int{http.StatusNoContent}) + } + return nil +} + +// DeleteEntityWithoutCheck deletes the entity matching by +// PartitionKey and RowKey. There is no check on IfMatch +// parameter so the entity is always deleted. +// The function fails if there is no entity +// with the same PartitionKey and RowKey in the table. +func (c *TableServiceClient) DeleteEntityWithoutCheck(table AzureTable, entity TableEntity) error { + return c.DeleteEntity(table, entity, "*") +} + +// DeleteEntity deletes the entity matching by +// PartitionKey, RowKey and ifMatch field. +// The function fails if there is no entity +// with the same PartitionKey and RowKey in the table or +// the ifMatch is different. +func (c *TableServiceClient) DeleteEntity(table AzureTable, entity TableEntity, ifMatch string) error { + uri := c.client.getEndpoint(tableServiceName, pathForTable(table), url.Values{}) + uri += fmt.Sprintf("(PartitionKey='%s',RowKey='%s')", url.QueryEscape(entity.PartitionKey()), url.QueryEscape(entity.RowKey())) + + headers := c.getStandardHeaders() + + headers["Content-Length"] = "0" + headers["If-Match"] = ifMatch + + resp, err := c.client.execInternalJSON(http.MethodDelete, uri, headers, nil, c.auth) + + if err != nil { + return err + } + defer resp.body.Close() + + if err := checkRespCode(resp.statusCode, []int{http.StatusNoContent}); err != nil { + return err + } + + return nil +} + +// InsertOrReplaceEntity inserts an entity in the specified table +// or replaced the existing one. +func (c *TableServiceClient) InsertOrReplaceEntity(table AzureTable, entity TableEntity) error { + if sc, err := c.execTable(table, entity, true, http.MethodPut); err != nil { + return checkRespCode(sc, []int{http.StatusNoContent}) + } + return nil +} + +// InsertOrMergeEntity inserts an entity in the specified table +// or merges the existing one. +func (c *TableServiceClient) InsertOrMergeEntity(table AzureTable, entity TableEntity) error { + if sc, err := c.execTable(table, entity, true, "MERGE"); err != nil { + return checkRespCode(sc, []int{http.StatusNoContent}) + } + return nil +} + +func injectPartitionAndRowKeys(entity TableEntity, buf *bytes.Buffer) error { + if err := json.NewEncoder(buf).Encode(entity); err != nil { + return err + } + + dec := make(map[string]interface{}) + if err := json.NewDecoder(buf).Decode(&dec); err != nil { + return err + } + + // Inject PartitionKey and RowKey + dec[partitionKeyNode] = entity.PartitionKey() + dec[rowKeyNode] = entity.RowKey() + + // Remove tagged fields + // The tag is defined in the const section + // This is useful to avoid storing the PartitionKey and RowKey twice. + numFields := reflect.ValueOf(entity).Elem().NumField() + for i := 0; i < numFields; i++ { + f := reflect.ValueOf(entity).Elem().Type().Field(i) + + if f.Tag.Get(tag) == tagIgnore { + // we must look for its JSON name in the dictionary + // as the user can rename it using a tag + jsonName := f.Name + if f.Tag.Get("json") != "" { + jsonName = f.Tag.Get("json") + } + delete(dec, jsonName) + } + } + + buf.Reset() + + if err := json.NewEncoder(buf).Encode(&dec); err != nil { + return err + } + + return nil +} + +func deserializeEntity(retType reflect.Type, reader io.Reader) ([]TableEntity, error) { + buf := new(bytes.Buffer) + + var ret getTableEntriesResponse + if err := json.NewDecoder(reader).Decode(&ret); err != nil { + return nil, err + } + + tEntries := make([]TableEntity, len(ret.Elements)) + + for i, entry := range ret.Elements { + + buf.Reset() + if err := json.NewEncoder(buf).Encode(entry); err != nil { + return nil, err + } + + dec := make(map[string]interface{}) + if err := json.NewDecoder(buf).Decode(&dec); err != nil { + return nil, err + } + + var pKey, rKey string + // strip pk and rk + for key, val := range dec { + switch key { + case partitionKeyNode: + pKey = val.(string) + case rowKeyNode: + rKey = val.(string) + } + } + + delete(dec, partitionKeyNode) + delete(dec, rowKeyNode) + + buf.Reset() + if err := json.NewEncoder(buf).Encode(dec); err != nil { + return nil, err + } + + // Create a empty retType instance + tEntries[i] = reflect.New(retType.Elem()).Interface().(TableEntity) + // Popolate it with the values + if err := json.NewDecoder(buf).Decode(&tEntries[i]); err != nil { + return nil, err + } + + // Reset PartitionKey and RowKey + if err := tEntries[i].SetPartitionKey(pKey); err != nil { + return nil, err + } + if err := tEntries[i].SetRowKey(rKey); err != nil { + return nil, err + } + } + + return tEntries, nil +} + +func extractContinuationTokenFromHeaders(h http.Header) *ContinuationToken { + ct := ContinuationToken{h.Get(continuationTokenPartitionKeyHeader), h.Get(continuationTokenRowHeader)} + + if ct.NextPartitionKey != "" && ct.NextRowKey != "" { + return &ct + } + return nil +} diff --git a/vendor/github.com/Azure/azure-sdk-for-go/storage/util.go b/vendor/github.com/Azure/azure-sdk-for-go/storage/util.go new file mode 100644 index 000000000..57ca1b6d9 --- /dev/null +++ b/vendor/github.com/Azure/azure-sdk-for-go/storage/util.go @@ -0,0 +1,85 @@ +package storage + +import ( + "bytes" + "crypto/hmac" + "crypto/sha256" + "encoding/base64" + "encoding/xml" + "fmt" + "io" + "io/ioutil" + "net/http" + "net/url" + "reflect" + "time" +) + +func (c Client) computeHmac256(message string) string { + h := hmac.New(sha256.New, c.accountKey) + h.Write([]byte(message)) + return base64.StdEncoding.EncodeToString(h.Sum(nil)) +} + +func currentTimeRfc1123Formatted() string { + return timeRfc1123Formatted(time.Now().UTC()) +} + +func timeRfc1123Formatted(t time.Time) string { + return t.Format(http.TimeFormat) +} + +func mergeParams(v1, v2 url.Values) url.Values { + out := url.Values{} + for k, v := range v1 { + out[k] = v + } + for k, v := range v2 { + vals, ok := out[k] + if ok { + vals = append(vals, v...) + out[k] = vals + } else { + out[k] = v + } + } + return out +} + +func prepareBlockListRequest(blocks []Block) string { + s := `` + for _, v := range blocks { + s += fmt.Sprintf("<%s>%s", v.Status, v.ID, v.Status) + } + s += `` + return s +} + +func xmlUnmarshal(body io.Reader, v interface{}) error { + data, err := ioutil.ReadAll(body) + if err != nil { + return err + } + return xml.Unmarshal(data, v) +} + +func xmlMarshal(v interface{}) (io.Reader, int, error) { + b, err := xml.Marshal(v) + if err != nil { + return nil, 0, err + } + return bytes.NewReader(b), len(b), nil +} + +func headersFromStruct(v interface{}) map[string]string { + headers := make(map[string]string) + value := reflect.ValueOf(v) + for i := 0; i < value.NumField(); i++ { + key := value.Type().Field(i).Tag.Get("header") + val := value.Field(i).String() + if key != "" && val != "" { + headers[key] = val + } + } + return headers +} diff --git a/vendor/github.com/Azure/azure-sdk-for-go/storage/version.go b/vendor/github.com/Azure/azure-sdk-for-go/storage/version.go new file mode 100644 index 000000000..d52ebfdb2 --- /dev/null +++ b/vendor/github.com/Azure/azure-sdk-for-go/storage/version.go @@ -0,0 +1,5 @@ +package storage + +var ( + sdkVersion = "8.0.0-beta" +) diff --git a/vendor/github.com/minio/redigo/redis/commandinfo.go b/vendor/github.com/garyburd/redigo/internal/commandinfo.go similarity index 95% rename from vendor/github.com/minio/redigo/redis/commandinfo.go rename to vendor/github.com/garyburd/redigo/internal/commandinfo.go index 0ad2af5df..11e584257 100644 --- a/vendor/github.com/minio/redigo/redis/commandinfo.go +++ b/vendor/github.com/garyburd/redigo/internal/commandinfo.go @@ -12,7 +12,7 @@ // License for the specific language governing permissions and limitations // under the License. -package redis +package internal // import "github.com/garyburd/redigo/internal" import ( "strings" diff --git a/vendor/github.com/minio/redigo/redis/conn.go b/vendor/github.com/garyburd/redigo/redis/conn.go similarity index 91% rename from vendor/github.com/minio/redigo/redis/conn.go rename to vendor/github.com/garyburd/redigo/redis/conn.go index ed358c601..6ccace078 100644 --- a/vendor/github.com/minio/redigo/redis/conn.go +++ b/vendor/github.com/garyburd/redigo/redis/conn.go @@ -17,6 +17,7 @@ package redis import ( "bufio" "bytes" + "crypto/tls" "errors" "fmt" "io" @@ -75,6 +76,9 @@ type dialOptions struct { dial func(network, addr string) (net.Conn, error) db int password string + dialTLS bool + skipVerify bool + tlsConfig *tls.Config } // DialReadTimeout specifies the timeout for reading a single command reply. @@ -123,6 +127,22 @@ func DialPassword(password string) DialOption { }} } +// DialTLSConfig specifies the config to use when a TLS connection is dialed. +// Has no effect when not dialing a TLS connection. +func DialTLSConfig(c *tls.Config) DialOption { + return DialOption{func(do *dialOptions) { + do.tlsConfig = c + }} +} + +// DialTLSSkipVerify to disable server name verification when connecting +// over TLS. Has no effect when not dialing a TLS connection. +func DialTLSSkipVerify(skip bool) DialOption { + return DialOption{func(do *dialOptions) { + do.skipVerify = skip + }} +} + // Dial connects to the Redis server at the given network and // address using the specified options. func Dial(network, address string, options ...DialOption) (Conn, error) { @@ -137,6 +157,26 @@ func Dial(network, address string, options ...DialOption) (Conn, error) { if err != nil { return nil, err } + + if do.dialTLS { + tlsConfig := cloneTLSClientConfig(do.tlsConfig, do.skipVerify) + if tlsConfig.ServerName == "" { + host, _, err := net.SplitHostPort(address) + if err != nil { + netConn.Close() + return nil, err + } + tlsConfig.ServerName = host + } + + tlsConn := tls.Client(netConn, tlsConfig) + if err := tlsConn.Handshake(); err != nil { + netConn.Close() + return nil, err + } + netConn = tlsConn + } + c := &conn{ conn: netConn, bw: bufio.NewWriter(netConn), @@ -162,6 +202,10 @@ func Dial(network, address string, options ...DialOption) (Conn, error) { return c, nil } +func dialTLS(do *dialOptions) { + do.dialTLS = true +} + var pathDBRegexp = regexp.MustCompile(`/(\d*)\z`) // DialURL connects to a Redis server at the given URL using the Redis @@ -173,7 +217,7 @@ func DialURL(rawurl string, options ...DialOption) (Conn, error) { return nil, err } - if u.Scheme != "redis" { + if u.Scheme != "redis" && u.Scheme != "rediss" { return nil, fmt.Errorf("invalid redis URL scheme: %s", u.Scheme) } @@ -213,6 +257,10 @@ func DialURL(rawurl string, options ...DialOption) (Conn, error) { return nil, fmt.Errorf("invalid database: %s", u.Path[1:]) } + if u.Scheme == "rediss" { + options = append([]DialOption{{dialTLS}}, options...) + } + return Dial("tcp", address, options...) } diff --git a/vendor/github.com/minio/redigo/redis/doc.go b/vendor/github.com/garyburd/redigo/redis/doc.go similarity index 91% rename from vendor/github.com/minio/redigo/redis/doc.go rename to vendor/github.com/garyburd/redigo/redis/doc.go index 6571c9de2..d4f489280 100644 --- a/vendor/github.com/minio/redigo/redis/doc.go +++ b/vendor/github.com/garyburd/redigo/redis/doc.go @@ -14,7 +14,7 @@ // Package redis is a client for the Redis database. // -// The Redigo FAQ (https://github.com/minio/redigo/wiki/FAQ) contains more +// The Redigo FAQ (https://github.com/garyburd/redigo/wiki/FAQ) contains more // documentation about this package. // // Connections @@ -99,7 +99,7 @@ // // Concurrency // -// Connections support one concurrent caller to the Recieve method and one +// Connections support one concurrent caller to the Receive method and one // concurrent caller to the Send and Flush methods. No other concurrency is // supported including concurrent calls to the Do method. // @@ -127,7 +127,7 @@ // send and flush a subscription management command. The receive method // converts a pushed message to convenient types for use in a type switch. // -// psc := redis.PubSubConn{c} +// psc := redis.PubSubConn{Conn: c} // psc.Subscribe("example") // for { // switch v := psc.Receive().(type) { @@ -165,4 +165,13 @@ // if _, err := redis.Scan(reply, &value1, &value2); err != nil { // // handle error // } -package redis // import "github.com/minio/redigo/redis" +// +// Errors +// +// Connection methods return error replies from the server as type redis.Error. +// +// Call the connection Err() method to determine if the connection encountered +// non-recoverable error such as a network error or protocol parsing error. If +// Err() returns a non-nil value, then the connection is not usable and should +// be closed. +package redis // import "github.com/garyburd/redigo/redis" diff --git a/vendor/github.com/garyburd/redigo/redis/go17.go b/vendor/github.com/garyburd/redigo/redis/go17.go new file mode 100644 index 000000000..3f951e5ef --- /dev/null +++ b/vendor/github.com/garyburd/redigo/redis/go17.go @@ -0,0 +1,33 @@ +// +build go1.7 + +package redis + +import "crypto/tls" + +// similar cloneTLSClientConfig in the stdlib, but also honor skipVerify for the nil case +func cloneTLSClientConfig(cfg *tls.Config, skipVerify bool) *tls.Config { + if cfg == nil { + return &tls.Config{InsecureSkipVerify: skipVerify} + } + return &tls.Config{ + Rand: cfg.Rand, + Time: cfg.Time, + Certificates: cfg.Certificates, + NameToCertificate: cfg.NameToCertificate, + GetCertificate: cfg.GetCertificate, + RootCAs: cfg.RootCAs, + NextProtos: cfg.NextProtos, + ServerName: cfg.ServerName, + ClientAuth: cfg.ClientAuth, + ClientCAs: cfg.ClientCAs, + InsecureSkipVerify: cfg.InsecureSkipVerify, + CipherSuites: cfg.CipherSuites, + PreferServerCipherSuites: cfg.PreferServerCipherSuites, + ClientSessionCache: cfg.ClientSessionCache, + MinVersion: cfg.MinVersion, + MaxVersion: cfg.MaxVersion, + CurvePreferences: cfg.CurvePreferences, + DynamicRecordSizingDisabled: cfg.DynamicRecordSizingDisabled, + Renegotiation: cfg.Renegotiation, + } +} diff --git a/vendor/github.com/minio/redigo/redis/log.go b/vendor/github.com/garyburd/redigo/redis/log.go similarity index 100% rename from vendor/github.com/minio/redigo/redis/log.go rename to vendor/github.com/garyburd/redigo/redis/log.go diff --git a/vendor/github.com/minio/redigo/redis/pool.go b/vendor/github.com/garyburd/redigo/redis/pool.go similarity index 81% rename from vendor/github.com/minio/redigo/redis/pool.go rename to vendor/github.com/garyburd/redigo/redis/pool.go index 749d8a9c5..283a41d5a 100644 --- a/vendor/github.com/minio/redigo/redis/pool.go +++ b/vendor/github.com/garyburd/redigo/redis/pool.go @@ -24,6 +24,8 @@ import ( "strconv" "sync" "time" + + "github.com/garyburd/redigo/internal" ) var nowFunc = time.Now // for testing @@ -44,40 +46,26 @@ var ( // // The following example shows how to use a pool in a web application. The // application creates a pool at application startup and makes it available to -// request handlers using a global variable. +// request handlers using a package level variable. The pool configuration used +// here is an example, not a recommendation. // -// func newPool(server, password string) *redis.Pool { -// return &redis.Pool{ -// MaxIdle: 3, -// IdleTimeout: 240 * time.Second, -// Dial: func () (redis.Conn, error) { -// c, err := redis.Dial("tcp", server) -// if err != nil { -// return nil, err -// } -// if _, err := c.Do("AUTH", password); err != nil { -// c.Close() -// return nil, err -// } -// return c, err -// }, -// TestOnBorrow: func(c redis.Conn, t time.Time) error { -// _, err := c.Do("PING") -// return err -// }, -// } +// func newPool(addr string) *redis.Pool { +// return &redis.Pool{ +// MaxIdle: 3, +// IdleTimeout: 240 * time.Second, +// Dial: func () (redis.Conn, error) { return redis.Dial("tcp", addr) }, +// } // } // // var ( -// pool *redis.Pool -// redisServer = flag.String("redisServer", ":6379", "") -// redisPassword = flag.String("redisPassword", "", "") +// pool *redis.Pool +// redisServer = flag.String("redisServer", ":6379", "") // ) // // func main() { -// flag.Parse() -// pool = newPool(*redisServer, *redisPassword) -// ... +// flag.Parse() +// pool = newPool(*redisServer) +// ... // } // // A request handler gets a connection from the pool and closes the connection @@ -86,7 +74,44 @@ var ( // func serveHome(w http.ResponseWriter, r *http.Request) { // conn := pool.Get() // defer conn.Close() -// .... +// ... +// } +// +// Use the Dial function to authenticate connections with the AUTH command or +// select a database with the SELECT command: +// +// pool := &redis.Pool{ +// // Other pool configuration not shown in this example. +// Dial: func () (redis.Conn, error) { +// c, err := redis.Dial("tcp", server) +// if err != nil { +// return nil, err +// } +// if _, err := c.Do("AUTH", password); err != nil { +// c.Close() +// return nil, err +// } +// if _, err := c.Do("SELECT", db); err != nil { +// c.Close() +// return nil, err +// } +// return c, nil +// } +// } +// +// Use the TestOnBorrow function to check the health of an idle connection +// before the connection is returned to the application. This example PINGs +// connections that have been idle more than a minute: +// +// pool := &redis.Pool{ +// // Other pool configuration not shown in this example. +// TestOnBorrow: func(c redis.Conn, t time.Time) error { +// if time.Since(t) < time.Minute { +// return nil +// } +// _, err := c.Do("PING") +// return err +// }, // } // type Pool struct { @@ -326,14 +351,14 @@ func (pc *pooledConnection) Close() error { } pc.c = errorConnection{errConnClosed} - if pc.state&MultiState != 0 { + if pc.state&internal.MultiState != 0 { c.Send("DISCARD") - pc.state &^= (MultiState | WatchState) - } else if pc.state&WatchState != 0 { + pc.state &^= (internal.MultiState | internal.WatchState) + } else if pc.state&internal.WatchState != 0 { c.Send("UNWATCH") - pc.state &^= WatchState + pc.state &^= internal.WatchState } - if pc.state&SubscribeState != 0 { + if pc.state&internal.SubscribeState != 0 { c.Send("UNSUBSCRIBE") c.Send("PUNSUBSCRIBE") // To detect the end of the message stream, ask the server to echo @@ -347,7 +372,7 @@ func (pc *pooledConnection) Close() error { break } if p, ok := p.([]byte); ok && bytes.Equal(p, sentinel) { - pc.state &^= SubscribeState + pc.state &^= internal.SubscribeState break } } @@ -362,13 +387,13 @@ func (pc *pooledConnection) Err() error { } func (pc *pooledConnection) Do(commandName string, args ...interface{}) (reply interface{}, err error) { - ci := LookupCommandInfo(commandName) + ci := internal.LookupCommandInfo(commandName) pc.state = (pc.state | ci.Set) &^ ci.Clear return pc.c.Do(commandName, args...) } func (pc *pooledConnection) Send(commandName string, args ...interface{}) error { - ci := LookupCommandInfo(commandName) + ci := internal.LookupCommandInfo(commandName) pc.state = (pc.state | ci.Set) &^ ci.Clear return pc.c.Send(commandName, args...) } diff --git a/vendor/github.com/garyburd/redigo/redis/pre_go17.go b/vendor/github.com/garyburd/redigo/redis/pre_go17.go new file mode 100644 index 000000000..0212f60fb --- /dev/null +++ b/vendor/github.com/garyburd/redigo/redis/pre_go17.go @@ -0,0 +1,31 @@ +// +build !go1.7 + +package redis + +import "crypto/tls" + +// similar cloneTLSClientConfig in the stdlib, but also honor skipVerify for the nil case +func cloneTLSClientConfig(cfg *tls.Config, skipVerify bool) *tls.Config { + if cfg == nil { + return &tls.Config{InsecureSkipVerify: skipVerify} + } + return &tls.Config{ + Rand: cfg.Rand, + Time: cfg.Time, + Certificates: cfg.Certificates, + NameToCertificate: cfg.NameToCertificate, + GetCertificate: cfg.GetCertificate, + RootCAs: cfg.RootCAs, + NextProtos: cfg.NextProtos, + ServerName: cfg.ServerName, + ClientAuth: cfg.ClientAuth, + ClientCAs: cfg.ClientCAs, + InsecureSkipVerify: cfg.InsecureSkipVerify, + CipherSuites: cfg.CipherSuites, + PreferServerCipherSuites: cfg.PreferServerCipherSuites, + ClientSessionCache: cfg.ClientSessionCache, + MinVersion: cfg.MinVersion, + MaxVersion: cfg.MaxVersion, + CurvePreferences: cfg.CurvePreferences, + } +} diff --git a/vendor/github.com/minio/redigo/redis/pubsub.go b/vendor/github.com/garyburd/redigo/redis/pubsub.go similarity index 100% rename from vendor/github.com/minio/redigo/redis/pubsub.go rename to vendor/github.com/garyburd/redigo/redis/pubsub.go diff --git a/vendor/github.com/minio/redigo/redis/redis.go b/vendor/github.com/garyburd/redigo/redis/redis.go similarity index 83% rename from vendor/github.com/minio/redigo/redis/redis.go rename to vendor/github.com/garyburd/redigo/redis/redis.go index c90a48ed4..b7298298c 100644 --- a/vendor/github.com/minio/redigo/redis/redis.go +++ b/vendor/github.com/garyburd/redigo/redis/redis.go @@ -24,10 +24,7 @@ type Conn interface { // Close closes the connection. Close() error - // Err returns a non-nil value if the connection is broken. The returned - // value is either the first non-nil value returned from the underlying - // network connection or a protocol parsing error. Applications should - // close broken connections. + // Err returns a non-nil value when the connection is not usable. Err() error // Do sends a command to the server and returns the received reply. diff --git a/vendor/github.com/minio/redigo/redis/reply.go b/vendor/github.com/garyburd/redigo/redis/reply.go similarity index 100% rename from vendor/github.com/minio/redigo/redis/reply.go rename to vendor/github.com/garyburd/redigo/redis/reply.go diff --git a/vendor/github.com/minio/redigo/redis/scan.go b/vendor/github.com/garyburd/redigo/redis/scan.go similarity index 100% rename from vendor/github.com/minio/redigo/redis/scan.go rename to vendor/github.com/garyburd/redigo/redis/scan.go diff --git a/vendor/github.com/minio/redigo/redis/script.go b/vendor/github.com/garyburd/redigo/redis/script.go similarity index 100% rename from vendor/github.com/minio/redigo/redis/script.go rename to vendor/github.com/garyburd/redigo/redis/script.go diff --git a/vendor/github.com/minio/cli/app.go b/vendor/github.com/minio/cli/app.go index 26a4d4d92..ce5f89f9a 100644 --- a/vendor/github.com/minio/cli/app.go +++ b/vendor/github.com/minio/cli/app.go @@ -45,8 +45,10 @@ type App struct { Flags []Flag // Boolean to enable bash completion commands EnableBashCompletion bool - // Boolean to hide built-in help command + // Boolean to hide built-in help flag HideHelp bool + // Boolean to hide built-in help command + HideHelpCommand bool // Boolean to hide built-in version flag and the VERSION section of help HideVersion bool // Populate on app startup, only gettable through method Categories() @@ -144,9 +146,11 @@ func (a *App) Setup() { } a.Commands = newCmds - if a.Command(helpCommand.Name) == nil && !a.HideHelp { - a.Commands = append(a.Commands, helpCommand) - if (HelpFlag != BoolFlag{}) { + if a.Command(helpCommand.Name) == nil { + if !a.HideHelpCommand { + a.Commands = append(a.Commands, helpCommand) + } + if !a.HideHelp && (HelpFlag != BoolFlag{}) { a.appendFlag(HelpFlag) } } @@ -285,9 +289,11 @@ func (a *App) RunAndExitOnError() { func (a *App) RunAsSubcommand(ctx *Context) (err error) { // append help to commands if len(a.Commands) > 0 { - if a.Command(helpCommand.Name) == nil && !a.HideHelp { - a.Commands = append(a.Commands, helpCommand) - if (HelpFlag != BoolFlag{}) { + if a.Command(helpCommand.Name) == nil { + if !a.HideHelpCommand { + a.Commands = append(a.Commands, helpCommand) + } + if !a.HideHelp && (HelpFlag != BoolFlag{}) { a.appendFlag(HelpFlag) } } diff --git a/vendor/github.com/minio/cli/command.go b/vendor/github.com/minio/cli/command.go index 1e4ca5cc4..e10e68811 100644 --- a/vendor/github.com/minio/cli/command.go +++ b/vendor/github.com/minio/cli/command.go @@ -51,8 +51,10 @@ type Command struct { // removed n version 2 since it only works under specific conditions so we // backport here by exposing it as an option for compatibility. SkipArgReorder bool - // Boolean to hide built-in help command + // Boolean to hide built-in help flag HideHelp bool + // Boolean to hide built-in help command + HideHelpCommand bool // Boolean to hide this command from help or completion Hidden bool @@ -261,6 +263,7 @@ func (c Command) startApp(ctx *Context) error { app.Commands = c.Subcommands app.Flags = c.Flags app.HideHelp = c.HideHelp + app.HideHelpCommand = c.HideHelpCommand app.Version = ctx.App.Version app.HideVersion = ctx.App.HideVersion @@ -301,5 +304,13 @@ func (c Command) startApp(ctx *Context) error { // VisibleFlags returns a slice of the Flags with Hidden=false func (c Command) VisibleFlags() []Flag { - return visibleFlags(c.Flags) + flags := c.Flags + if !c.HideHelp && (HelpFlag != BoolFlag{}) { + // append help to flags + flags = append( + flags, + HelpFlag, + ) + } + return visibleFlags(flags) } diff --git a/vendor/github.com/minio/cli/help.go b/vendor/github.com/minio/cli/help.go index c42f9c9a7..ff35ea110 100644 --- a/vendor/github.com/minio/cli/help.go +++ b/vendor/github.com/minio/cli/help.go @@ -13,69 +13,68 @@ import ( // cli.go uses text/template to render templates. You can // render custom help text by setting this variable. var AppHelpTemplate = `NAME: - {{.Name}}{{if .Usage}} - {{.Usage}}{{end}} + {{.Name}}{{if .Usage}} - {{.Usage}}{{end}} USAGE: - {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Version}}{{if not .HideVersion}} + {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Version}}{{if not .HideVersion}} VERSION: - {{.Version}}{{end}}{{end}}{{if .Description}} + {{.Version}}{{end}}{{end}}{{if .Description}} DESCRIPTION: - {{.Description}}{{end}}{{if len .Authors}} + {{.Description}}{{end}}{{if len .Authors}} AUTHOR{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}: - {{range $index, $author := .Authors}}{{if $index}} - {{end}}{{$author}}{{end}}{{end}}{{if .VisibleCommands}} + {{range $index, $author := .Authors}}{{if $index}} + {{end}}{{$author}}{{end}}{{end}}{{if .VisibleCommands}} COMMANDS:{{range .VisibleCategories}}{{if .Name}} - {{.Name}}:{{end}}{{range .VisibleCommands}} - {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{if .VisibleFlags}} + {{.Name}}:{{end}}{{range .VisibleCommands}} + {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{if .VisibleFlags}} -GLOBAL OPTIONS: - {{range $index, $option := .VisibleFlags}}{{if $index}} - {{end}}{{$option}}{{end}}{{end}}{{if .Copyright}} +GLOBAL FLAGS: + {{range $index, $option := .VisibleFlags}}{{if $index}} + {{end}}{{$option}}{{end}}{{end}}{{if .Copyright}} COPYRIGHT: - {{.Copyright}}{{end}} + {{.Copyright}}{{end}} ` // CommandHelpTemplate is the text template for the command help topic. // cli.go uses text/template to render templates. You can // render custom help text by setting this variable. var CommandHelpTemplate = `NAME: - {{.HelpName}} - {{.Usage}} + {{.HelpName}} - {{.Usage}} USAGE: - {{.HelpName}}{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{if .Category}} + {{.HelpName}}{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{if .Category}} CATEGORY: - {{.Category}}{{end}}{{if .Description}} + {{.Category}}{{end}}{{if .Description}} DESCRIPTION: - {{.Description}}{{end}}{{if .VisibleFlags}} + {{.Description}}{{end}}{{if .VisibleFlags}} -OPTIONS: - {{range .VisibleFlags}}{{.}} - {{end}}{{end}} +FLAGS: + {{range .VisibleFlags}}{{.}} + {{end}}{{end}} ` // SubcommandHelpTemplate is the text template for the subcommand help topic. // cli.go uses text/template to render templates. You can // render custom help text by setting this variable. var SubcommandHelpTemplate = `NAME: - {{.HelpName}} - {{if .Description}}{{.Description}}{{else}}{{.Usage}}{{end}} + {{.HelpName}} - {{if .Description}}{{.Description}}{{else}}{{.Usage}}{{end}} USAGE: - {{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}} + {{.HelpName}} COMMAND{{if .VisibleFlags}} [COMMAND FLAGS | -h]{{end}} [ARGUMENTS...] -COMMANDS:{{range .VisibleCategories}}{{if .Name}} - {{.Name}}:{{end}}{{range .VisibleCommands}} - {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}} -{{end}}{{if .VisibleFlags}} -OPTIONS: - {{range .VisibleFlags}}{{.}} - {{end}}{{end}} +COMMANDS: + {{range .VisibleCommands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}} + {{end}}{{if .VisibleFlags}} +FLAGS: + {{range .VisibleFlags}}{{.}} + {{end}}{{end}} ` var helpCommand = Command{ diff --git a/vendor/github.com/tidwall/gjson/README.md b/vendor/github.com/tidwall/gjson/README.md index 1ee5ae3dd..b38f920bf 100644 --- a/vendor/github.com/tidwall/gjson/README.md +++ b/vendor/github.com/tidwall/gjson/README.md @@ -11,7 +11,7 @@

    get a json value quickly

    -GJSON is a Go package the provides a [very fast](#performance) and simple way to get a value from a json document. The reason for this library it to give efficient json indexing for the [BuntDB](https://github.com/tidwall/buntdb) project. +GJSON is a Go package that provides a [very fast](#performance) and simple way to get a value from a json document. The purpose for this library it to give efficient json indexing for the [BuntDB](https://github.com/tidwall/buntdb) project. Getting Started =============== @@ -27,7 +27,7 @@ $ go get -u github.com/tidwall/gjson This will retrieve the library. ## Get a value -Get searches json for the specified path. A path is in dot syntax, such as "name.last" or "age". This function expects that the json is well-formed and validates. Invalid json will not panic, but it may return back unexpected results. When the value is found it's returned immediately. +Get searches json for the specified path. A path is in dot syntax, such as "name.last" or "age". This function expects that the json is well-formed and validates. Invalid json will not panic, but it may return back unexpected results. When the value is found it's returned immediately. ```go package main @@ -47,6 +47,7 @@ This will print: ``` Prichard ``` +*There's also the [GetMany](#get-multiple-values-at-once) function to get multiple values at once, and [GetBytes](#working-with-bytes) for working with JSON byte slices.* ## Path Syntax @@ -63,25 +64,33 @@ The dot and wildcard characters can be escaped with '\'. "children": ["Sara","Alex","Jack"], "fav.movie": "Deer Hunter", "friends": [ - {"first": "James", "last": "Murphy"}, - {"first": "Roger", "last": "Craig"} + {"first": "Dale", "last": "Murphy", "age": 44}, + {"first": "Roger", "last": "Craig", "age": 68}, + {"first": "Jane", "last": "Murphy", "age": 47} ] } ``` ``` "name.last" >> "Anderson" "age" >> 37 +"children" >> ["Sara","Alex","Jack"] "children.#" >> 3 "children.1" >> "Alex" "child*.2" >> "Jack" "c?ildren.0" >> "Sara" "fav\.movie" >> "Deer Hunter" -"friends.#.first" >> [ "James", "Roger" ] +"friends.#.first" >> ["Dale","Roger","Jane"] "friends.1.last" >> "Craig" ``` -To query an array: + +You can also query an array for the first match by using `#[...]`, or find all matches with `#[...]#`. +Queries support the `==`, `!=`, `<`, `<=`, `>`, `>=` comparison operators and the simple pattern matching `%` operator. + ``` -`friends.#[last="Murphy"].first` >> "James" +friends.#[last=="Murphy"].first >> "Dale" +friends.#[last=="Murphy"]#.first >> ["Dale","Jane"] +friends.#[age>45]#.last >> ["Craig","Murphy"] +friends.#[first%"D*"].last >> "Murphy" ``` ## Result Type @@ -105,7 +114,7 @@ result.Type // can be String, Number, True, False, Null, or JSON result.Str // holds the string result.Num // holds the float64 number result.Raw // holds the raw json -result.Multi // holds nested array values +result.Index // index of raw value in original json, zero means index unknown ``` There are a variety of handy functions that work on a result: @@ -113,16 +122,25 @@ There are a variety of handy functions that work on a result: ```go result.Value() interface{} result.Int() int64 +result.Uint() uint64 result.Float() float64 result.String() string result.Bool() bool result.Array() []gjson.Result result.Map() map[string]gjson.Result result.Get(path string) Result +result.ForEach(iterator func(key, value Result) bool) +result.Less(token Result, caseSensitive bool) bool ``` The `result.Value()` function returns an `interface{}` which requires type assertion and is one of the following Go types: + + +The `result.Array()` function returns back an array of values. +If the result represents a non-existent value, then an empty array will be returned. +If the result is not a JSON array, the return value will be an array containing one result. + ```go boolean >> bool number >> float64 @@ -169,6 +187,20 @@ name := gjson.Get(json, `programmers.#[lastName="Hunter"].firstName`) println(name.String()) // prints "Elliotte" ``` +## Iterate through an object or array + +The `ForEach` function allows for quickly iterating through an object or array. +The key and value are passed to the iterator function for objects. +Only the value is passed for arrays. +Returning `false` from an iterator will stop iteration. + +```go +result := gjson.Get(json, "programmers") +result.ForEach(func(key, value gjson.Result) bool{ + println(value.String()) + return true // keep iterating +}) +``` ## Simple Parse and Get @@ -184,7 +216,7 @@ gjson.Get(json, "name.last") ## Check for the existence of a value -Sometimes you just want to know you if a value exists. +Sometimes you just want to know if a value exists. ```go value := gjson.Get(json, "name.last") @@ -211,6 +243,40 @@ if !ok{ } ``` +## Working with Bytes + +If your JSON is contained in a `[]byte` slice, there's the [GetBytes](https://godoc.org/github.com/tidwall/gjson#GetBytes) function. This is preferred over `Get(string(data), path)`. + +```go +var json []byte = ... +result := gjson.GetBytes(json, path) +``` + +If you are using the `gjson.GetBytes(json, path)` function and you want to avoid converting `result.Raw` to a `[]byte`, then you can use this pattern: + +```go +var json []byte = ... +result := gjson.GetBytes(json, path) +var raw []byte +if result.Index > 0 { + raw = json[result.Index:result.Index+len(result.Raw)] +} else { + raw = []byte(result.Raw) +} +``` + +This is a best-effort no allocation sub slice of the original json. This method utilizes the `result.Index` field, which is the position of the raw data in the original json. It's possible that the value of `result.Index` equals zero, in which case the `result.Raw` is converted to a `[]byte`. + +## Get multiple values at once + +The `GetMany` function can be used to get multiple values at the same time, and is optimized to scan over a JSON payload once. + +```go +results := gjson.GetMany(json, "name.first", "name.last", "age") +``` + +The return value is a `[]Result`, which will always contain exactly the same number of items as the input paths. + ## Performance Benchmarks of GJSON alongside [encoding/json](https://golang.org/pkg/encoding/json/), @@ -229,6 +295,17 @@ BenchmarkEasyJSONLexer-8 3000000 938 ns/op 613 B/op BenchmarkJSONParserGet-8 3000000 442 ns/op 21 B/op 0 allocs/op ``` +Benchmarks for the `GetMany` function: + +``` +BenchmarkGJSONGetMany4Paths-8 4000000 319 ns/op 112 B/op 0 allocs/op +BenchmarkGJSONGetMany8Paths-8 8000000 218 ns/op 56 B/op 0 allocs/op +BenchmarkGJSONGetMany16Paths-8 16000000 160 ns/op 56 B/op 0 allocs/op +BenchmarkGJSONGetMany32Paths-8 32000000 130 ns/op 64 B/op 0 allocs/op +BenchmarkGJSONGetMany64Paths-8 64000000 117 ns/op 64 B/op 0 allocs/op +BenchmarkGJSONGetMany128Paths-8 128000000 109 ns/op 64 B/op 0 allocs/op +``` + JSON document used: ```json @@ -267,6 +344,20 @@ widget.image.hOffset widget.text.onMouseUp ``` +For the `GetMany` benchmarks these paths are used: + +``` +widget.window.name +widget.image.hOffset +widget.text.onMouseUp +widget.window.title +widget.image.alignment +widget.text.style +widget.window.height +widget.image.src +widget.text.data +widget.text.size +``` *These benchmarks were run on a MacBook Pro 15" 2.8 GHz Intel Core i7 using Go 1.7.* diff --git a/vendor/github.com/tidwall/gjson/gjson.go b/vendor/github.com/tidwall/gjson/gjson.go index 5ad877455..9b28df2ca 100644 --- a/vendor/github.com/tidwall/gjson/gjson.go +++ b/vendor/github.com/tidwall/gjson/gjson.go @@ -4,6 +4,10 @@ package gjson import ( "reflect" "strconv" + + // It's totally safe to use this package, but in case your + // project or organization restricts the use of 'unsafe', + // there's the "github.com/tidwall/gjson-safe" package. "unsafe" "github.com/tidwall/match" @@ -27,6 +31,26 @@ const ( JSON ) +// String returns a string representation of the type. +func (t Type) String() string { + switch t { + default: + return "" + case Null: + return "Null" + case False: + return "False" + case Number: + return "Number" + case String: + return "String" + case True: + return "True" + case JSON: + return "JSON" + } +} + // Result represents a json value that is returned from Get(). type Result struct { // Type is the json type @@ -37,6 +61,8 @@ type Result struct { Str string // Num is the json number Num float64 + // Index of raw value in original json, zero means index unknown + Index int } // String returns a string representation of the value. @@ -86,6 +112,21 @@ func (t Result) Int() int64 { } } +// Uint returns an unsigned integer representation. +func (t Result) Uint() uint64 { + switch t.Type { + default: + return 0 + case True: + return 1 + case String: + n, _ := strconv.ParseUint(t.Str, 10, 64) + return n + case Number: + return uint64(t.Num) + } +} + // Float returns an float64 representation. func (t Result) Float() float64 { switch t.Type { @@ -101,16 +142,91 @@ func (t Result) Float() float64 { } } -// Array returns back an array of children. The result must be a JSON array. +// Array returns back an array of values. +// If the result represents a non-existent value, then an empty array will be returned. +// If the result is not a JSON array, the return value will be an array containing one result. func (t Result) Array() []Result { - if t.Type != JSON { + if !t.Exists() { return nil } + if t.Type != JSON { + return []Result{t} + } r := t.arrayOrMap('[', false) return r.a } -// Map returns back an map of children. The result should be a JSON array. +// ForEach iterates through values. +// If the result represents a non-existent value, then no values will be iterated. +// If the result is an Object, the iterator will pass the key and value of each item. +// If the result is an Array, the iterator will only pass the value of each item. +// If the result is not a JSON array or object, the iterator will pass back one value equal to the result. +func (t Result) ForEach(iterator func(key, value Result) bool) { + if !t.Exists() { + return + } + if t.Type != JSON { + iterator(Result{}, t) + return + } + json := t.Raw + var keys bool + var i int + var key, value Result + for ; i < len(json); i++ { + if json[i] == '{' { + i++ + key.Type = String + keys = true + break + } else if json[i] == '[' { + i++ + break + } + if json[i] > ' ' { + return + } + } + var str string + var vesc bool + var ok bool + for ; i < len(json); i++ { + if keys { + if json[i] != '"' { + continue + } + s := i + i, str, vesc, ok = parseString(json, i+1) + if !ok { + return + } + if vesc { + key.Str = unescape(str[1 : len(str)-1]) + } else { + key.Str = str[1 : len(str)-1] + } + key.Raw = str + key.Index = s + } + for ; i < len(json); i++ { + if json[i] <= ' ' || json[i] == ',' || json[i] == ':' { + continue + } + break + } + s := i + i, value, ok = parseAny(json, i, true) + if !ok { + return + } + value.Index = s + if !iterator(key, value) { + return + } + } +} + +// Map returns back an map of values. The result should be a JSON array. func (t Result) Map() map[string]Result { if t.Type != JSON { return map[string]Result{} @@ -232,7 +348,7 @@ end: return } -// Parse parses the json and returns a result +// Parse parses the json and returns a result. func Parse(json string) Result { var value Result for i := 0; i < len(json); i++ { @@ -270,6 +386,12 @@ func Parse(json string) Result { return value } +// ParseBytes parses the json and returns a result. +// If working with bytes, this method preferred over Parse(string(data)) +func ParseBytes(json []byte) Result { + return Parse(string(json)) +} + func squash(json string) string { // expects that the lead character is a '[' or '{' // squash the value, ignoring all nested arrays and objects. @@ -387,7 +509,13 @@ func tostr(json string) (raw string, str string) { break } } - return json[:i+1], unescape(json[1:i]) + var ret string + if i+1 < len(json) { + ret = json[:i+1] + } else { + ret = json[:i] + } + return ret, unescape(json[1:i]) } } return json, json[1:] @@ -506,6 +634,7 @@ type arrayPathResult struct { path string op string value string + all bool } } @@ -536,8 +665,12 @@ func parseArrayPath(path string) (r arrayPathResult) { } s := i for ; i < len(path); i++ { - if path[i] <= ' ' || path[i] == '=' || - path[i] == '<' || path[i] == '>' || + if path[i] <= ' ' || + path[i] == '!' || + path[i] == '=' || + path[i] == '<' || + path[i] == '>' || + path[i] == '%' || path[i] == ']' { break } @@ -551,7 +684,11 @@ func parseArrayPath(path string) (r arrayPathResult) { } if i < len(path) { s = i - if path[i] == '<' || path[i] == '>' { + if path[i] == '!' { + if i < len(path)-1 && path[i+1] == '=' { + i++ + } + } else if path[i] == '<' || path[i] == '>' { if i < len(path)-1 && path[i+1] == '=' { i++ } @@ -596,6 +733,9 @@ func parseArrayPath(path string) (r arrayPathResult) { } } } else if path[i] == ']' { + if i+1 < len(path) && path[i+1] == '#' { + r.query.all = true + } break } } @@ -877,6 +1017,8 @@ func queryMatches(rp *arrayPathResult, value Result) bool { switch rp.query.op { case "=": return value.Str == rpv + case "!=": + return value.Str != rpv case "<": return value.Str < rpv case "<=": @@ -885,12 +1027,16 @@ func queryMatches(rp *arrayPathResult, value Result) bool { return value.Str > rpv case ">=": return value.Str >= rpv + case "%": + return match.Match(value.Str, rpv) } case Number: rpvn, _ := strconv.ParseFloat(rpv, 64) switch rp.query.op { case "=": return value.Num == rpvn + case "!=": + return value.Num == rpvn case "<": return value.Num < rpvn case "<=": @@ -904,6 +1050,8 @@ func queryMatches(rp *arrayPathResult, value Result) bool { switch rp.query.op { case "=": return rpv == "true" + case "!=": + return rpv != "true" case ">": return rpv == "false" case ">=": @@ -913,6 +1061,8 @@ func queryMatches(rp *arrayPathResult, value Result) bool { switch rp.query.op { case "=": return rpv == "false" + case "!=": + return rpv != "false" case "<": return rpv == "true" case "<=": @@ -927,6 +1077,7 @@ func parseArray(c *parseContext, i int, path string) (int, bool) { var h int var alog []int var partidx int + var multires []byte rp := parseArrayPath(path) if !rp.arrch { n, err := strconv.ParseUint(rp.part, 10, 64) @@ -983,12 +1134,21 @@ func parseArray(c *parseContext, i int, path string) (int, bool) { res := Get(val, rp.query.path) if queryMatches(&rp, res) { if rp.more { - c.value = Get(val, rp.path) + res = Get(val, rp.path) } else { - c.value.Raw = val - c.value.Type = JSON + res = Result{Raw: val, Type: JSON} + } + if rp.query.all { + if len(multires) == 0 { + multires = append(multires, '[') + } else { + multires = append(multires, ',') + } + multires = append(multires, res.Raw...) + } else { + c.value = res + return i, true } - return i, true } } else if hit { if rp.alogok { @@ -1051,13 +1211,14 @@ func parseArray(c *parseContext, i int, path string) (int, bool) { if rp.alogok { var jsons = make([]byte, 0, 64) jsons = append(jsons, '[') - for j := 0; j < len(alog); j++ { + for j, k := 0, 0; j < len(alog); j++ { res := Get(c.json[alog[j]:], rp.alogkey) if res.Exists() { - if j > 0 { + if k > 0 { jsons = append(jsons, ',') } jsons = append(jsons, []byte(res.Raw)...) + k++ } } jsons = append(jsons, ']') @@ -1071,9 +1232,16 @@ func parseArray(c *parseContext, i int, path string) (int, bool) { c.value.Raw = val c.value.Type = Number c.value.Num = float64(h - 1) + c.calcd = true return i + 1, true } } + if len(multires) > 0 && !c.value.Exists() { + c.value = Result{ + Raw: string(append(multires, ']')), + Type: JSON, + } + } return i + 1, false } break @@ -1085,6 +1253,7 @@ func parseArray(c *parseContext, i int, path string) (int, bool) { type parseContext struct { json string value Result + calcd bool } // Get searches json for the specified path. @@ -1093,7 +1262,7 @@ type parseContext struct { // Invalid json will not panic, but it may return back unexpected results. // When the value is found it's returned immediately. // -// A path is a series of keys seperated by a dot. +// A path is a series of keys searated by a dot. // A key may contain special wildcard characters '*' and '?'. // To access an array value use the index as the key. // To get the number of elements in an array or to access a child path, use the '#' character. @@ -1110,11 +1279,12 @@ type parseContext struct { // } // "name.last" >> "Anderson" // "age" >> 37 +// "children" >> ["Sara","Alex","Jack"] // "children.#" >> 3 // "children.1" >> "Alex" // "child*.2" >> "Jack" // "c?ildren.0" >> "Sara" -// "friends.#.first" >> [ "James", "Roger" ] +// "friends.#.first" >> ["James","Roger"] // func Get(json, path string) Result { var i int @@ -1131,8 +1301,53 @@ func Get(json, path string) Result { break } } + if len(c.value.Raw) > 0 && !c.calcd { + jhdr := *(*reflect.StringHeader)(unsafe.Pointer(&json)) + rhdr := *(*reflect.StringHeader)(unsafe.Pointer(&(c.value.Raw))) + c.value.Index = int(rhdr.Data - jhdr.Data) + if c.value.Index < 0 || c.value.Index >= len(json) { + c.value.Index = 0 + } + } return c.value } +func fromBytesGet(result Result) Result { + // safely get the string headers + rawhi := *(*reflect.StringHeader)(unsafe.Pointer(&result.Raw)) + strhi := *(*reflect.StringHeader)(unsafe.Pointer(&result.Str)) + // create byte slice headers + rawh := reflect.SliceHeader{Data: rawhi.Data, Len: rawhi.Len} + strh := reflect.SliceHeader{Data: strhi.Data, Len: strhi.Len} + if strh.Data == 0 { + // str is nil + if rawh.Data == 0 { + // raw is nil + result.Raw = "" + } else { + // raw has data, safely copy the slice header to a string + result.Raw = string(*(*[]byte)(unsafe.Pointer(&rawh))) + } + result.Str = "" + } else if rawh.Data == 0 { + // raw is nil + result.Raw = "" + // str has data, safely copy the slice header to a string + result.Str = string(*(*[]byte)(unsafe.Pointer(&strh))) + } else if strh.Data >= rawh.Data && + int(strh.Data)+strh.Len <= int(rawh.Data)+rawh.Len { + // Str is a substring of Raw. + start := int(strh.Data - rawh.Data) + // safely copy the raw slice header + result.Raw = string(*(*[]byte)(unsafe.Pointer(&rawh))) + // substring the raw + result.Str = result.Raw[start : start+strh.Len] + } else { + // safely copy both the raw and str slice headers to strings + result.Raw = string(*(*[]byte)(unsafe.Pointer(&rawh))) + result.Str = string(*(*[]byte)(unsafe.Pointer(&strh))) + } + return result +} // GetBytes searches json for the specified path. // If working with bytes, this method preferred over Get(string(data), path) @@ -1141,29 +1356,7 @@ func GetBytes(json []byte, path string) Result { if json != nil { // unsafe cast to string result = Get(*(*string)(unsafe.Pointer(&json)), path) - // copy of string data for safety. - rawh := *(*reflect.SliceHeader)(unsafe.Pointer(&result.Raw)) - strh := *(*reflect.SliceHeader)(unsafe.Pointer(&result.Str)) - if strh.Data == 0 { - if rawh.Data == 0 { - result.Raw = "" - } else { - result.Raw = string(*(*[]byte)(unsafe.Pointer(&result.Raw))) - } - result.Str = "" - } else if rawh.Data == 0 { - result.Raw = "" - result.Str = string(*(*[]byte)(unsafe.Pointer(&result.Str))) - } else if strh.Data >= rawh.Data && - int(strh.Data)+strh.Len <= int(rawh.Data)+rawh.Len { - // Str is a substring of Raw. - start := int(strh.Data - rawh.Data) - result.Raw = string(*(*[]byte)(unsafe.Pointer(&result.Raw))) - result.Str = result.Raw[start : start+strh.Len] - } else { - result.Raw = string(*(*[]byte)(unsafe.Pointer(&result.Raw))) - result.Str = string(*(*[]byte)(unsafe.Pointer(&result.Str))) - } + result = fromBytesGet(result) } return result } @@ -1289,3 +1482,465 @@ func stringLessInsensitive(a, b string) bool { } return len(a) < len(b) } + +// parseAny parses the next value from a json string. +// A Result is returned when the hit param is set. +// The return values are (i int, res Result, ok bool) +func parseAny(json string, i int, hit bool) (int, Result, bool) { + var res Result + var val string + for ; i < len(json); i++ { + if json[i] == '{' || json[i] == '[' { + i, val = parseSquash(json, i) + if hit { + res.Raw = val + res.Type = JSON + } + return i, res, true + } + if json[i] <= ' ' { + continue + } + switch json[i] { + case '"': + i++ + var vesc bool + var ok bool + i, val, vesc, ok = parseString(json, i) + if !ok { + return i, res, false + } + if hit { + res.Type = String + res.Raw = val + if vesc { + res.Str = unescape(val[1 : len(val)-1]) + } else { + res.Str = val[1 : len(val)-1] + } + } + return i, res, true + case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + i, val = parseNumber(json, i) + if hit { + res.Raw = val + res.Type = Number + res.Num, _ = strconv.ParseFloat(val, 64) + } + return i, res, true + case 't', 'f', 'n': + vc := json[i] + i, val = parseLiteral(json, i) + if hit { + res.Raw = val + switch vc { + case 't': + res.Type = True + case 'f': + res.Type = False + } + return i, res, true + } + } + } + return i, res, false +} + +var ( // used for testing + testWatchForFallback bool + testLastWasFallback bool +) + +// areSimplePaths returns true if all the paths are simple enough +// to parse quickly for GetMany(). Allows alpha-numeric, dots, +// underscores, and the dollar sign. It does not allow non-alnum, +// escape characters, or keys which start with a numbers. +// For example: +// "name.last" == OK +// "user.id0" == OK +// "user.ID" == OK +// "user.first_name" == OK +// "user.firstName" == OK +// "user.0item" == BAD +// "user.#id" == BAD +// "user\.name" == BAD +func areSimplePaths(paths []string) bool { + for _, path := range paths { + var fi int // first key index, for keys with numeric prefix + for i := 0; i < len(path); i++ { + if path[i] >= 'a' && path[i] <= 'z' { + // a-z is likely to be the highest frequency charater. + continue + } + if path[i] == '.' { + fi = i + 1 + continue + } + if path[i] >= 'A' && path[i] <= 'Z' { + continue + } + if path[i] == '_' || path[i] == '$' { + continue + } + if i > fi && path[i] >= '0' && path[i] <= '9' { + continue + } + return false + } + } + return true +} + +// GetMany searches json for the multiple paths. +// The return value is a Result array where the number of items +// will be equal to the number of input paths. +func GetMany(json string, paths ...string) []Result { + if len(paths) < 4 { + if testWatchForFallback { + testLastWasFallback = false + } + switch len(paths) { + case 0: + // return nil when no paths are specified. + return nil + case 1: + return []Result{Get(json, paths[0])} + case 2: + return []Result{Get(json, paths[0]), Get(json, paths[1])} + case 3: + return []Result{Get(json, paths[0]), Get(json, paths[1]), Get(json, paths[2])} + } + } + var results []Result + var ok bool + var i int + if len(paths) > 512 { + // we can only support up to 512 paths. Is that too many? + goto fallback + } + if !areSimplePaths(paths) { + // If there is even one path that is not considered "simple" then + // we need to use the fallback method. + goto fallback + } + // locate the object token. + for ; i < len(json); i++ { + if json[i] == '{' { + i++ + break + } + if json[i] <= ' ' { + continue + } + goto fallback + } + // use the call function table. + if len(paths) <= 8 { + results, ok = getMany8(json, i, paths) + } else if len(paths) <= 16 { + results, ok = getMany16(json, i, paths) + } else if len(paths) <= 32 { + results, ok = getMany32(json, i, paths) + } else if len(paths) <= 64 { + results, ok = getMany64(json, i, paths) + } else if len(paths) <= 128 { + results, ok = getMany128(json, i, paths) + } else if len(paths) <= 256 { + results, ok = getMany256(json, i, paths) + } else if len(paths) <= 512 { + results, ok = getMany512(json, i, paths) + } + if !ok { + // there was some fault while parsing. we should try the + // fallback method. This could result in performance + // degregation in some cases. + goto fallback + } + if testWatchForFallback { + testLastWasFallback = false + } + return results +fallback: + results = results[:0] + for i := 0; i < len(paths); i++ { + results = append(results, Get(json, paths[i])) + } + if testWatchForFallback { + testLastWasFallback = true + } + return results +} + +// GetManyBytes searches json for the specified path. +// If working with bytes, this method preferred over +// GetMany(string(data), paths...) +func GetManyBytes(json []byte, paths ...string) []Result { + if json == nil { + return GetMany("", paths...) + } + results := GetMany(*(*string)(unsafe.Pointer(&json)), paths...) + for i := range results { + results[i] = fromBytesGet(results[i]) + } + return results +} + +// parseGetMany parses a json object for keys that match against the callers +// paths. It's a best-effort attempt and quickly locating and assigning the +// values to the []Result array. If there are failures such as bad json, or +// invalid input paths, or too much recursion, the function will exit with a +// return value of 'false'. +func parseGetMany( + json string, i int, + level uint, kplen int, + paths []string, completed []bool, matches []uint64, results []Result, +) (int, bool) { + if level > 62 { + // The recursion level is limited because the matches []uint64 + // array cannot handle more the 64-bits. + return i, false + } + // At this point the last character read was a '{'. + // Read all object keys and try to match against the paths. + var key string + var val string + var vesc, ok bool +next_key: + for ; i < len(json); i++ { + if json[i] == '"' { + // read the key + i, val, vesc, ok = parseString(json, i+1) + if !ok { + return i, false + } + if vesc { + // the value is escaped + key = unescape(val[1 : len(val)-1]) + } else { + // just a plain old ascii key + key = val[1 : len(val)-1] + } + var hasMatch bool + var parsedVal bool + var valOrgIndex int + var valPathIndex int + for j := 0; j < len(key); j++ { + if key[j] == '.' { + // we need to look for keys with dot and ignore them. + if i, _, ok = parseAny(json, i, false); !ok { + return i, false + } + continue next_key + } + } + var usedPaths int + // loop through paths and look for matches + for j := 0; j < len(paths); j++ { + if completed[j] { + usedPaths++ + // ignore completed paths + continue + } + if level > 0 && (matches[j]>>(level-1))&1 == 0 { + // ignore unmatched paths + usedPaths++ + continue + } + + // try to match the key to the path + // this is spaghetti code but the idea is to minimize + // calls and variable assignments when comparing the + // key to paths + if len(paths[j])-kplen >= len(key) { + i, k := kplen, 0 + for ; k < len(key); k, i = k+1, i+1 { + if key[k] != paths[j][i] { + // no match + goto nomatch + } + } + if i < len(paths[j]) { + if paths[j][i] == '.' { + // matched, but there still more keys in the path + goto match_not_atend + } + } + // matched and at the end of the path + goto match_atend + } + // no match, jump to the nomatch label + goto nomatch + match_atend: + // found a match + // at the end of the path. we must take the value. + usedPaths++ + if !parsedVal { + // the value has not been parsed yet. let's do so. + valOrgIndex = i // keep track of the current position. + i, results[j], ok = parseAny(json, i, true) + if !ok { + return i, false + } + parsedVal = true + valPathIndex = j + } else { + results[j] = results[valPathIndex] + } + // mark as complete + completed[j] = true + // jump over the match_not_atend label + goto nomatch + match_not_atend: + // found a match + // still in the middle of the path. + usedPaths++ + // mark the path as matched + matches[j] |= 1 << level + if !hasMatch { + hasMatch = true + } + nomatch: // noop label + } + + if !parsedVal { + if hasMatch { + // we found a match and the value has not been parsed yet. + // let's find out if the next value type is an object. + for ; i < len(json); i++ { + if json[i] <= ' ' || json[i] == ':' { + continue + } + break + } + if i < len(json) { + if json[i] == '{' { + // it's an object. let's go deeper + i, ok = parseGetMany(json, i+1, level+1, kplen+len(key)+1, paths, completed, matches, results) + if !ok { + return i, false + } + } else { + // not an object. just parse and ignore. + if i, _, ok = parseAny(json, i, false); !ok { + return i, false + } + } + } + } else { + // Since there was no matches we can just parse the value and + // ignore the result. + if i, _, ok = parseAny(json, i, false); !ok { + return i, false + } + } + } else if hasMatch && len(results[valPathIndex].Raw) > 0 && results[valPathIndex].Raw[0] == '{' { + // The value was already parsed and the value type is an object. + // Rewind the json index and let's parse deeper. + i = valOrgIndex + for ; i < len(json); i++ { + if json[i] == '{' { + break + } + } + i, ok = parseGetMany(json, i+1, level+1, kplen+len(key)+1, paths, completed, matches, results) + if !ok { + return i, false + } + } + if usedPaths == len(paths) { + // all paths have been used, either completed or matched. + // we should stop parsing this object to save CPU cycles. + if level > 0 && i < len(json) { + i, _ = parseSquash(json, i) + } + return i, true + } + } else if json[i] == '}' { + // reached the end of the object. end it here. + return i + 1, true + } + } + return i, true +} + +// Call table for GetMany. Using an isolated function allows for allocating +// arrays with know capacities on the stack, as opposed to dynamically +// allocating on the heap. This can provide a tremendous performance boost +// by avoiding the GC. +func getMany8(json string, i int, paths []string) ([]Result, bool) { + const max = 8 + var completed = make([]bool, 0, max) + var matches = make([]uint64, 0, max) + var results = make([]Result, 0, max) + completed = completed[0:len(paths):max] + matches = matches[0:len(paths):max] + results = results[0:len(paths):max] + _, ok := parseGetMany(json, i, 0, 0, paths, completed, matches, results) + return results, ok +} +func getMany16(json string, i int, paths []string) ([]Result, bool) { + const max = 16 + var completed = make([]bool, 0, max) + var matches = make([]uint64, 0, max) + var results = make([]Result, 0, max) + completed = completed[0:len(paths):max] + matches = matches[0:len(paths):max] + results = results[0:len(paths):max] + _, ok := parseGetMany(json, i, 0, 0, paths, completed, matches, results) + return results, ok +} +func getMany32(json string, i int, paths []string) ([]Result, bool) { + const max = 32 + var completed = make([]bool, 0, max) + var matches = make([]uint64, 0, max) + var results = make([]Result, 0, max) + completed = completed[0:len(paths):max] + matches = matches[0:len(paths):max] + results = results[0:len(paths):max] + _, ok := parseGetMany(json, i, 0, 0, paths, completed, matches, results) + return results, ok +} +func getMany64(json string, i int, paths []string) ([]Result, bool) { + const max = 64 + var completed = make([]bool, 0, max) + var matches = make([]uint64, 0, max) + var results = make([]Result, 0, max) + completed = completed[0:len(paths):max] + matches = matches[0:len(paths):max] + results = results[0:len(paths):max] + _, ok := parseGetMany(json, i, 0, 0, paths, completed, matches, results) + return results, ok +} +func getMany128(json string, i int, paths []string) ([]Result, bool) { + const max = 128 + var completed = make([]bool, 0, max) + var matches = make([]uint64, 0, max) + var results = make([]Result, 0, max) + completed = completed[0:len(paths):max] + matches = matches[0:len(paths):max] + results = results[0:len(paths):max] + _, ok := parseGetMany(json, i, 0, 0, paths, completed, matches, results) + return results, ok +} +func getMany256(json string, i int, paths []string) ([]Result, bool) { + const max = 256 + var completed = make([]bool, 0, max) + var matches = make([]uint64, 0, max) + var results = make([]Result, 0, max) + completed = completed[0:len(paths):max] + matches = matches[0:len(paths):max] + results = results[0:len(paths):max] + _, ok := parseGetMany(json, i, 0, 0, paths, completed, matches, results) + return results, ok +} +func getMany512(json string, i int, paths []string) ([]Result, bool) { + const max = 512 + var completed = make([]bool, 0, max) + var matches = make([]uint64, 0, max) + var results = make([]Result, 0, max) + completed = completed[0:len(paths):max] + matches = matches[0:len(paths):max] + results = results[0:len(paths):max] + _, ok := parseGetMany(json, i, 0, 0, paths, completed, matches, results) + return results, ok +} diff --git a/vendor/gopkg.in/yaml.v2/LICENSE b/vendor/gopkg.in/yaml.v2/LICENSE new file mode 100644 index 000000000..866d74a7a --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/LICENSE @@ -0,0 +1,13 @@ +Copyright 2011-2016 Canonical Ltd. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/vendor/gopkg.in/yaml.v2/LICENSE.libyaml b/vendor/gopkg.in/yaml.v2/LICENSE.libyaml new file mode 100644 index 000000000..8da58fbf6 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/LICENSE.libyaml @@ -0,0 +1,31 @@ +The following files were ported to Go from C files of libyaml, and thus +are still covered by their original copyright and license: + + apic.go + emitterc.go + parserc.go + readerc.go + scannerc.go + writerc.go + yamlh.go + yamlprivateh.go + +Copyright (c) 2006 Kirill Simonov + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/gopkg.in/yaml.v2/README.md b/vendor/gopkg.in/yaml.v2/README.md new file mode 100644 index 000000000..1884de6a7 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/README.md @@ -0,0 +1,131 @@ +# YAML support for the Go language + +Introduction +------------ + +The yaml package enables Go programs to comfortably encode and decode YAML +values. It was developed within [Canonical](https://www.canonical.com) as +part of the [juju](https://juju.ubuntu.com) project, and is based on a +pure Go port of the well-known [libyaml](http://pyyaml.org/wiki/LibYAML) +C library to parse and generate YAML data quickly and reliably. + +Compatibility +------------- + +The yaml package supports most of YAML 1.1 and 1.2, including support for +anchors, tags, map merging, etc. Multi-document unmarshalling is not yet +implemented, and base-60 floats from YAML 1.1 are purposefully not +supported since they're a poor design and are gone in YAML 1.2. + +Installation and usage +---------------------- + +The import path for the package is *gopkg.in/yaml.v2*. + +To install it, run: + + go get gopkg.in/yaml.v2 + +API documentation +----------------- + +If opened in a browser, the import path itself leads to the API documentation: + + * [https://gopkg.in/yaml.v2](https://gopkg.in/yaml.v2) + +API stability +------------- + +The package API for yaml v2 will remain stable as described in [gopkg.in](https://gopkg.in). + + +License +------- + +The yaml package is licensed under the Apache License 2.0. Please see the LICENSE file for details. + + +Example +------- + +```Go +package main + +import ( + "fmt" + "log" + + "gopkg.in/yaml.v2" +) + +var data = ` +a: Easy! +b: + c: 2 + d: [3, 4] +` + +type T struct { + A string + B struct { + RenamedC int `yaml:"c"` + D []int `yaml:",flow"` + } +} + +func main() { + t := T{} + + err := yaml.Unmarshal([]byte(data), &t) + if err != nil { + log.Fatalf("error: %v", err) + } + fmt.Printf("--- t:\n%v\n\n", t) + + d, err := yaml.Marshal(&t) + if err != nil { + log.Fatalf("error: %v", err) + } + fmt.Printf("--- t dump:\n%s\n\n", string(d)) + + m := make(map[interface{}]interface{}) + + err = yaml.Unmarshal([]byte(data), &m) + if err != nil { + log.Fatalf("error: %v", err) + } + fmt.Printf("--- m:\n%v\n\n", m) + + d, err = yaml.Marshal(&m) + if err != nil { + log.Fatalf("error: %v", err) + } + fmt.Printf("--- m dump:\n%s\n\n", string(d)) +} +``` + +This example will generate the following output: + +``` +--- t: +{Easy! {2 [3 4]}} + +--- t dump: +a: Easy! +b: + c: 2 + d: [3, 4] + + +--- m: +map[a:Easy! b:map[c:2 d:[3 4]]] + +--- m dump: +a: Easy! +b: + c: 2 + d: + - 3 + - 4 +``` + diff --git a/vendor/gopkg.in/yaml.v2/apic.go b/vendor/gopkg.in/yaml.v2/apic.go new file mode 100644 index 000000000..95ec014e8 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/apic.go @@ -0,0 +1,742 @@ +package yaml + +import ( + "io" + "os" +) + +func yaml_insert_token(parser *yaml_parser_t, pos int, token *yaml_token_t) { + //fmt.Println("yaml_insert_token", "pos:", pos, "typ:", token.typ, "head:", parser.tokens_head, "len:", len(parser.tokens)) + + // Check if we can move the queue at the beginning of the buffer. + if parser.tokens_head > 0 && len(parser.tokens) == cap(parser.tokens) { + if parser.tokens_head != len(parser.tokens) { + copy(parser.tokens, parser.tokens[parser.tokens_head:]) + } + parser.tokens = parser.tokens[:len(parser.tokens)-parser.tokens_head] + parser.tokens_head = 0 + } + parser.tokens = append(parser.tokens, *token) + if pos < 0 { + return + } + copy(parser.tokens[parser.tokens_head+pos+1:], parser.tokens[parser.tokens_head+pos:]) + parser.tokens[parser.tokens_head+pos] = *token +} + +// Create a new parser object. +func yaml_parser_initialize(parser *yaml_parser_t) bool { + *parser = yaml_parser_t{ + raw_buffer: make([]byte, 0, input_raw_buffer_size), + buffer: make([]byte, 0, input_buffer_size), + } + return true +} + +// Destroy a parser object. +func yaml_parser_delete(parser *yaml_parser_t) { + *parser = yaml_parser_t{} +} + +// String read handler. +func yaml_string_read_handler(parser *yaml_parser_t, buffer []byte) (n int, err error) { + if parser.input_pos == len(parser.input) { + return 0, io.EOF + } + n = copy(buffer, parser.input[parser.input_pos:]) + parser.input_pos += n + return n, nil +} + +// File read handler. +func yaml_file_read_handler(parser *yaml_parser_t, buffer []byte) (n int, err error) { + return parser.input_file.Read(buffer) +} + +// Set a string input. +func yaml_parser_set_input_string(parser *yaml_parser_t, input []byte) { + if parser.read_handler != nil { + panic("must set the input source only once") + } + parser.read_handler = yaml_string_read_handler + parser.input = input + parser.input_pos = 0 +} + +// Set a file input. +func yaml_parser_set_input_file(parser *yaml_parser_t, file *os.File) { + if parser.read_handler != nil { + panic("must set the input source only once") + } + parser.read_handler = yaml_file_read_handler + parser.input_file = file +} + +// Set the source encoding. +func yaml_parser_set_encoding(parser *yaml_parser_t, encoding yaml_encoding_t) { + if parser.encoding != yaml_ANY_ENCODING { + panic("must set the encoding only once") + } + parser.encoding = encoding +} + +// Create a new emitter object. +func yaml_emitter_initialize(emitter *yaml_emitter_t) bool { + *emitter = yaml_emitter_t{ + buffer: make([]byte, output_buffer_size), + raw_buffer: make([]byte, 0, output_raw_buffer_size), + states: make([]yaml_emitter_state_t, 0, initial_stack_size), + events: make([]yaml_event_t, 0, initial_queue_size), + } + return true +} + +// Destroy an emitter object. +func yaml_emitter_delete(emitter *yaml_emitter_t) { + *emitter = yaml_emitter_t{} +} + +// String write handler. +func yaml_string_write_handler(emitter *yaml_emitter_t, buffer []byte) error { + *emitter.output_buffer = append(*emitter.output_buffer, buffer...) + return nil +} + +// File write handler. +func yaml_file_write_handler(emitter *yaml_emitter_t, buffer []byte) error { + _, err := emitter.output_file.Write(buffer) + return err +} + +// Set a string output. +func yaml_emitter_set_output_string(emitter *yaml_emitter_t, output_buffer *[]byte) { + if emitter.write_handler != nil { + panic("must set the output target only once") + } + emitter.write_handler = yaml_string_write_handler + emitter.output_buffer = output_buffer +} + +// Set a file output. +func yaml_emitter_set_output_file(emitter *yaml_emitter_t, file io.Writer) { + if emitter.write_handler != nil { + panic("must set the output target only once") + } + emitter.write_handler = yaml_file_write_handler + emitter.output_file = file +} + +// Set the output encoding. +func yaml_emitter_set_encoding(emitter *yaml_emitter_t, encoding yaml_encoding_t) { + if emitter.encoding != yaml_ANY_ENCODING { + panic("must set the output encoding only once") + } + emitter.encoding = encoding +} + +// Set the canonical output style. +func yaml_emitter_set_canonical(emitter *yaml_emitter_t, canonical bool) { + emitter.canonical = canonical +} + +//// Set the indentation increment. +func yaml_emitter_set_indent(emitter *yaml_emitter_t, indent int) { + if indent < 2 || indent > 9 { + indent = 2 + } + emitter.best_indent = indent +} + +// Set the preferred line width. +func yaml_emitter_set_width(emitter *yaml_emitter_t, width int) { + if width < 0 { + width = -1 + } + emitter.best_width = width +} + +// Set if unescaped non-ASCII characters are allowed. +func yaml_emitter_set_unicode(emitter *yaml_emitter_t, unicode bool) { + emitter.unicode = unicode +} + +// Set the preferred line break character. +func yaml_emitter_set_break(emitter *yaml_emitter_t, line_break yaml_break_t) { + emitter.line_break = line_break +} + +///* +// * Destroy a token object. +// */ +// +//YAML_DECLARE(void) +//yaml_token_delete(yaml_token_t *token) +//{ +// assert(token); // Non-NULL token object expected. +// +// switch (token.type) +// { +// case YAML_TAG_DIRECTIVE_TOKEN: +// yaml_free(token.data.tag_directive.handle); +// yaml_free(token.data.tag_directive.prefix); +// break; +// +// case YAML_ALIAS_TOKEN: +// yaml_free(token.data.alias.value); +// break; +// +// case YAML_ANCHOR_TOKEN: +// yaml_free(token.data.anchor.value); +// break; +// +// case YAML_TAG_TOKEN: +// yaml_free(token.data.tag.handle); +// yaml_free(token.data.tag.suffix); +// break; +// +// case YAML_SCALAR_TOKEN: +// yaml_free(token.data.scalar.value); +// break; +// +// default: +// break; +// } +// +// memset(token, 0, sizeof(yaml_token_t)); +//} +// +///* +// * Check if a string is a valid UTF-8 sequence. +// * +// * Check 'reader.c' for more details on UTF-8 encoding. +// */ +// +//static int +//yaml_check_utf8(yaml_char_t *start, size_t length) +//{ +// yaml_char_t *end = start+length; +// yaml_char_t *pointer = start; +// +// while (pointer < end) { +// unsigned char octet; +// unsigned int width; +// unsigned int value; +// size_t k; +// +// octet = pointer[0]; +// width = (octet & 0x80) == 0x00 ? 1 : +// (octet & 0xE0) == 0xC0 ? 2 : +// (octet & 0xF0) == 0xE0 ? 3 : +// (octet & 0xF8) == 0xF0 ? 4 : 0; +// value = (octet & 0x80) == 0x00 ? octet & 0x7F : +// (octet & 0xE0) == 0xC0 ? octet & 0x1F : +// (octet & 0xF0) == 0xE0 ? octet & 0x0F : +// (octet & 0xF8) == 0xF0 ? octet & 0x07 : 0; +// if (!width) return 0; +// if (pointer+width > end) return 0; +// for (k = 1; k < width; k ++) { +// octet = pointer[k]; +// if ((octet & 0xC0) != 0x80) return 0; +// value = (value << 6) + (octet & 0x3F); +// } +// if (!((width == 1) || +// (width == 2 && value >= 0x80) || +// (width == 3 && value >= 0x800) || +// (width == 4 && value >= 0x10000))) return 0; +// +// pointer += width; +// } +// +// return 1; +//} +// + +// Create STREAM-START. +func yaml_stream_start_event_initialize(event *yaml_event_t, encoding yaml_encoding_t) bool { + *event = yaml_event_t{ + typ: yaml_STREAM_START_EVENT, + encoding: encoding, + } + return true +} + +// Create STREAM-END. +func yaml_stream_end_event_initialize(event *yaml_event_t) bool { + *event = yaml_event_t{ + typ: yaml_STREAM_END_EVENT, + } + return true +} + +// Create DOCUMENT-START. +func yaml_document_start_event_initialize(event *yaml_event_t, version_directive *yaml_version_directive_t, + tag_directives []yaml_tag_directive_t, implicit bool) bool { + *event = yaml_event_t{ + typ: yaml_DOCUMENT_START_EVENT, + version_directive: version_directive, + tag_directives: tag_directives, + implicit: implicit, + } + return true +} + +// Create DOCUMENT-END. +func yaml_document_end_event_initialize(event *yaml_event_t, implicit bool) bool { + *event = yaml_event_t{ + typ: yaml_DOCUMENT_END_EVENT, + implicit: implicit, + } + return true +} + +///* +// * Create ALIAS. +// */ +// +//YAML_DECLARE(int) +//yaml_alias_event_initialize(event *yaml_event_t, anchor *yaml_char_t) +//{ +// mark yaml_mark_t = { 0, 0, 0 } +// anchor_copy *yaml_char_t = NULL +// +// assert(event) // Non-NULL event object is expected. +// assert(anchor) // Non-NULL anchor is expected. +// +// if (!yaml_check_utf8(anchor, strlen((char *)anchor))) return 0 +// +// anchor_copy = yaml_strdup(anchor) +// if (!anchor_copy) +// return 0 +// +// ALIAS_EVENT_INIT(*event, anchor_copy, mark, mark) +// +// return 1 +//} + +// Create SCALAR. +func yaml_scalar_event_initialize(event *yaml_event_t, anchor, tag, value []byte, plain_implicit, quoted_implicit bool, style yaml_scalar_style_t) bool { + *event = yaml_event_t{ + typ: yaml_SCALAR_EVENT, + anchor: anchor, + tag: tag, + value: value, + implicit: plain_implicit, + quoted_implicit: quoted_implicit, + style: yaml_style_t(style), + } + return true +} + +// Create SEQUENCE-START. +func yaml_sequence_start_event_initialize(event *yaml_event_t, anchor, tag []byte, implicit bool, style yaml_sequence_style_t) bool { + *event = yaml_event_t{ + typ: yaml_SEQUENCE_START_EVENT, + anchor: anchor, + tag: tag, + implicit: implicit, + style: yaml_style_t(style), + } + return true +} + +// Create SEQUENCE-END. +func yaml_sequence_end_event_initialize(event *yaml_event_t) bool { + *event = yaml_event_t{ + typ: yaml_SEQUENCE_END_EVENT, + } + return true +} + +// Create MAPPING-START. +func yaml_mapping_start_event_initialize(event *yaml_event_t, anchor, tag []byte, implicit bool, style yaml_mapping_style_t) bool { + *event = yaml_event_t{ + typ: yaml_MAPPING_START_EVENT, + anchor: anchor, + tag: tag, + implicit: implicit, + style: yaml_style_t(style), + } + return true +} + +// Create MAPPING-END. +func yaml_mapping_end_event_initialize(event *yaml_event_t) bool { + *event = yaml_event_t{ + typ: yaml_MAPPING_END_EVENT, + } + return true +} + +// Destroy an event object. +func yaml_event_delete(event *yaml_event_t) { + *event = yaml_event_t{} +} + +///* +// * Create a document object. +// */ +// +//YAML_DECLARE(int) +//yaml_document_initialize(document *yaml_document_t, +// version_directive *yaml_version_directive_t, +// tag_directives_start *yaml_tag_directive_t, +// tag_directives_end *yaml_tag_directive_t, +// start_implicit int, end_implicit int) +//{ +// struct { +// error yaml_error_type_t +// } context +// struct { +// start *yaml_node_t +// end *yaml_node_t +// top *yaml_node_t +// } nodes = { NULL, NULL, NULL } +// version_directive_copy *yaml_version_directive_t = NULL +// struct { +// start *yaml_tag_directive_t +// end *yaml_tag_directive_t +// top *yaml_tag_directive_t +// } tag_directives_copy = { NULL, NULL, NULL } +// value yaml_tag_directive_t = { NULL, NULL } +// mark yaml_mark_t = { 0, 0, 0 } +// +// assert(document) // Non-NULL document object is expected. +// assert((tag_directives_start && tag_directives_end) || +// (tag_directives_start == tag_directives_end)) +// // Valid tag directives are expected. +// +// if (!STACK_INIT(&context, nodes, INITIAL_STACK_SIZE)) goto error +// +// if (version_directive) { +// version_directive_copy = yaml_malloc(sizeof(yaml_version_directive_t)) +// if (!version_directive_copy) goto error +// version_directive_copy.major = version_directive.major +// version_directive_copy.minor = version_directive.minor +// } +// +// if (tag_directives_start != tag_directives_end) { +// tag_directive *yaml_tag_directive_t +// if (!STACK_INIT(&context, tag_directives_copy, INITIAL_STACK_SIZE)) +// goto error +// for (tag_directive = tag_directives_start +// tag_directive != tag_directives_end; tag_directive ++) { +// assert(tag_directive.handle) +// assert(tag_directive.prefix) +// if (!yaml_check_utf8(tag_directive.handle, +// strlen((char *)tag_directive.handle))) +// goto error +// if (!yaml_check_utf8(tag_directive.prefix, +// strlen((char *)tag_directive.prefix))) +// goto error +// value.handle = yaml_strdup(tag_directive.handle) +// value.prefix = yaml_strdup(tag_directive.prefix) +// if (!value.handle || !value.prefix) goto error +// if (!PUSH(&context, tag_directives_copy, value)) +// goto error +// value.handle = NULL +// value.prefix = NULL +// } +// } +// +// DOCUMENT_INIT(*document, nodes.start, nodes.end, version_directive_copy, +// tag_directives_copy.start, tag_directives_copy.top, +// start_implicit, end_implicit, mark, mark) +// +// return 1 +// +//error: +// STACK_DEL(&context, nodes) +// yaml_free(version_directive_copy) +// while (!STACK_EMPTY(&context, tag_directives_copy)) { +// value yaml_tag_directive_t = POP(&context, tag_directives_copy) +// yaml_free(value.handle) +// yaml_free(value.prefix) +// } +// STACK_DEL(&context, tag_directives_copy) +// yaml_free(value.handle) +// yaml_free(value.prefix) +// +// return 0 +//} +// +///* +// * Destroy a document object. +// */ +// +//YAML_DECLARE(void) +//yaml_document_delete(document *yaml_document_t) +//{ +// struct { +// error yaml_error_type_t +// } context +// tag_directive *yaml_tag_directive_t +// +// context.error = YAML_NO_ERROR // Eliminate a compliler warning. +// +// assert(document) // Non-NULL document object is expected. +// +// while (!STACK_EMPTY(&context, document.nodes)) { +// node yaml_node_t = POP(&context, document.nodes) +// yaml_free(node.tag) +// switch (node.type) { +// case YAML_SCALAR_NODE: +// yaml_free(node.data.scalar.value) +// break +// case YAML_SEQUENCE_NODE: +// STACK_DEL(&context, node.data.sequence.items) +// break +// case YAML_MAPPING_NODE: +// STACK_DEL(&context, node.data.mapping.pairs) +// break +// default: +// assert(0) // Should not happen. +// } +// } +// STACK_DEL(&context, document.nodes) +// +// yaml_free(document.version_directive) +// for (tag_directive = document.tag_directives.start +// tag_directive != document.tag_directives.end +// tag_directive++) { +// yaml_free(tag_directive.handle) +// yaml_free(tag_directive.prefix) +// } +// yaml_free(document.tag_directives.start) +// +// memset(document, 0, sizeof(yaml_document_t)) +//} +// +///** +// * Get a document node. +// */ +// +//YAML_DECLARE(yaml_node_t *) +//yaml_document_get_node(document *yaml_document_t, index int) +//{ +// assert(document) // Non-NULL document object is expected. +// +// if (index > 0 && document.nodes.start + index <= document.nodes.top) { +// return document.nodes.start + index - 1 +// } +// return NULL +//} +// +///** +// * Get the root object. +// */ +// +//YAML_DECLARE(yaml_node_t *) +//yaml_document_get_root_node(document *yaml_document_t) +//{ +// assert(document) // Non-NULL document object is expected. +// +// if (document.nodes.top != document.nodes.start) { +// return document.nodes.start +// } +// return NULL +//} +// +///* +// * Add a scalar node to a document. +// */ +// +//YAML_DECLARE(int) +//yaml_document_add_scalar(document *yaml_document_t, +// tag *yaml_char_t, value *yaml_char_t, length int, +// style yaml_scalar_style_t) +//{ +// struct { +// error yaml_error_type_t +// } context +// mark yaml_mark_t = { 0, 0, 0 } +// tag_copy *yaml_char_t = NULL +// value_copy *yaml_char_t = NULL +// node yaml_node_t +// +// assert(document) // Non-NULL document object is expected. +// assert(value) // Non-NULL value is expected. +// +// if (!tag) { +// tag = (yaml_char_t *)YAML_DEFAULT_SCALAR_TAG +// } +// +// if (!yaml_check_utf8(tag, strlen((char *)tag))) goto error +// tag_copy = yaml_strdup(tag) +// if (!tag_copy) goto error +// +// if (length < 0) { +// length = strlen((char *)value) +// } +// +// if (!yaml_check_utf8(value, length)) goto error +// value_copy = yaml_malloc(length+1) +// if (!value_copy) goto error +// memcpy(value_copy, value, length) +// value_copy[length] = '\0' +// +// SCALAR_NODE_INIT(node, tag_copy, value_copy, length, style, mark, mark) +// if (!PUSH(&context, document.nodes, node)) goto error +// +// return document.nodes.top - document.nodes.start +// +//error: +// yaml_free(tag_copy) +// yaml_free(value_copy) +// +// return 0 +//} +// +///* +// * Add a sequence node to a document. +// */ +// +//YAML_DECLARE(int) +//yaml_document_add_sequence(document *yaml_document_t, +// tag *yaml_char_t, style yaml_sequence_style_t) +//{ +// struct { +// error yaml_error_type_t +// } context +// mark yaml_mark_t = { 0, 0, 0 } +// tag_copy *yaml_char_t = NULL +// struct { +// start *yaml_node_item_t +// end *yaml_node_item_t +// top *yaml_node_item_t +// } items = { NULL, NULL, NULL } +// node yaml_node_t +// +// assert(document) // Non-NULL document object is expected. +// +// if (!tag) { +// tag = (yaml_char_t *)YAML_DEFAULT_SEQUENCE_TAG +// } +// +// if (!yaml_check_utf8(tag, strlen((char *)tag))) goto error +// tag_copy = yaml_strdup(tag) +// if (!tag_copy) goto error +// +// if (!STACK_INIT(&context, items, INITIAL_STACK_SIZE)) goto error +// +// SEQUENCE_NODE_INIT(node, tag_copy, items.start, items.end, +// style, mark, mark) +// if (!PUSH(&context, document.nodes, node)) goto error +// +// return document.nodes.top - document.nodes.start +// +//error: +// STACK_DEL(&context, items) +// yaml_free(tag_copy) +// +// return 0 +//} +// +///* +// * Add a mapping node to a document. +// */ +// +//YAML_DECLARE(int) +//yaml_document_add_mapping(document *yaml_document_t, +// tag *yaml_char_t, style yaml_mapping_style_t) +//{ +// struct { +// error yaml_error_type_t +// } context +// mark yaml_mark_t = { 0, 0, 0 } +// tag_copy *yaml_char_t = NULL +// struct { +// start *yaml_node_pair_t +// end *yaml_node_pair_t +// top *yaml_node_pair_t +// } pairs = { NULL, NULL, NULL } +// node yaml_node_t +// +// assert(document) // Non-NULL document object is expected. +// +// if (!tag) { +// tag = (yaml_char_t *)YAML_DEFAULT_MAPPING_TAG +// } +// +// if (!yaml_check_utf8(tag, strlen((char *)tag))) goto error +// tag_copy = yaml_strdup(tag) +// if (!tag_copy) goto error +// +// if (!STACK_INIT(&context, pairs, INITIAL_STACK_SIZE)) goto error +// +// MAPPING_NODE_INIT(node, tag_copy, pairs.start, pairs.end, +// style, mark, mark) +// if (!PUSH(&context, document.nodes, node)) goto error +// +// return document.nodes.top - document.nodes.start +// +//error: +// STACK_DEL(&context, pairs) +// yaml_free(tag_copy) +// +// return 0 +//} +// +///* +// * Append an item to a sequence node. +// */ +// +//YAML_DECLARE(int) +//yaml_document_append_sequence_item(document *yaml_document_t, +// sequence int, item int) +//{ +// struct { +// error yaml_error_type_t +// } context +// +// assert(document) // Non-NULL document is required. +// assert(sequence > 0 +// && document.nodes.start + sequence <= document.nodes.top) +// // Valid sequence id is required. +// assert(document.nodes.start[sequence-1].type == YAML_SEQUENCE_NODE) +// // A sequence node is required. +// assert(item > 0 && document.nodes.start + item <= document.nodes.top) +// // Valid item id is required. +// +// if (!PUSH(&context, +// document.nodes.start[sequence-1].data.sequence.items, item)) +// return 0 +// +// return 1 +//} +// +///* +// * Append a pair of a key and a value to a mapping node. +// */ +// +//YAML_DECLARE(int) +//yaml_document_append_mapping_pair(document *yaml_document_t, +// mapping int, key int, value int) +//{ +// struct { +// error yaml_error_type_t +// } context +// +// pair yaml_node_pair_t +// +// assert(document) // Non-NULL document is required. +// assert(mapping > 0 +// && document.nodes.start + mapping <= document.nodes.top) +// // Valid mapping id is required. +// assert(document.nodes.start[mapping-1].type == YAML_MAPPING_NODE) +// // A mapping node is required. +// assert(key > 0 && document.nodes.start + key <= document.nodes.top) +// // Valid key id is required. +// assert(value > 0 && document.nodes.start + value <= document.nodes.top) +// // Valid value id is required. +// +// pair.key = key +// pair.value = value +// +// if (!PUSH(&context, +// document.nodes.start[mapping-1].data.mapping.pairs, pair)) +// return 0 +// +// return 1 +//} +// +// diff --git a/vendor/gopkg.in/yaml.v2/decode.go b/vendor/gopkg.in/yaml.v2/decode.go new file mode 100644 index 000000000..b13ab9f07 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/decode.go @@ -0,0 +1,683 @@ +package yaml + +import ( + "encoding" + "encoding/base64" + "fmt" + "math" + "reflect" + "strconv" + "time" +) + +const ( + documentNode = 1 << iota + mappingNode + sequenceNode + scalarNode + aliasNode +) + +type node struct { + kind int + line, column int + tag string + value string + implicit bool + children []*node + anchors map[string]*node +} + +// ---------------------------------------------------------------------------- +// Parser, produces a node tree out of a libyaml event stream. + +type parser struct { + parser yaml_parser_t + event yaml_event_t + doc *node +} + +func newParser(b []byte) *parser { + p := parser{} + if !yaml_parser_initialize(&p.parser) { + panic("failed to initialize YAML emitter") + } + + if len(b) == 0 { + b = []byte{'\n'} + } + + yaml_parser_set_input_string(&p.parser, b) + + p.skip() + if p.event.typ != yaml_STREAM_START_EVENT { + panic("expected stream start event, got " + strconv.Itoa(int(p.event.typ))) + } + p.skip() + return &p +} + +func (p *parser) destroy() { + if p.event.typ != yaml_NO_EVENT { + yaml_event_delete(&p.event) + } + yaml_parser_delete(&p.parser) +} + +func (p *parser) skip() { + if p.event.typ != yaml_NO_EVENT { + if p.event.typ == yaml_STREAM_END_EVENT { + failf("attempted to go past the end of stream; corrupted value?") + } + yaml_event_delete(&p.event) + } + if !yaml_parser_parse(&p.parser, &p.event) { + p.fail() + } +} + +func (p *parser) fail() { + var where string + var line int + if p.parser.problem_mark.line != 0 { + line = p.parser.problem_mark.line + } else if p.parser.context_mark.line != 0 { + line = p.parser.context_mark.line + } + if line != 0 { + where = "line " + strconv.Itoa(line) + ": " + } + var msg string + if len(p.parser.problem) > 0 { + msg = p.parser.problem + } else { + msg = "unknown problem parsing YAML content" + } + failf("%s%s", where, msg) +} + +func (p *parser) anchor(n *node, anchor []byte) { + if anchor != nil { + p.doc.anchors[string(anchor)] = n + } +} + +func (p *parser) parse() *node { + switch p.event.typ { + case yaml_SCALAR_EVENT: + return p.scalar() + case yaml_ALIAS_EVENT: + return p.alias() + case yaml_MAPPING_START_EVENT: + return p.mapping() + case yaml_SEQUENCE_START_EVENT: + return p.sequence() + case yaml_DOCUMENT_START_EVENT: + return p.document() + case yaml_STREAM_END_EVENT: + // Happens when attempting to decode an empty buffer. + return nil + default: + panic("attempted to parse unknown event: " + strconv.Itoa(int(p.event.typ))) + } + panic("unreachable") +} + +func (p *parser) node(kind int) *node { + return &node{ + kind: kind, + line: p.event.start_mark.line, + column: p.event.start_mark.column, + } +} + +func (p *parser) document() *node { + n := p.node(documentNode) + n.anchors = make(map[string]*node) + p.doc = n + p.skip() + n.children = append(n.children, p.parse()) + if p.event.typ != yaml_DOCUMENT_END_EVENT { + panic("expected end of document event but got " + strconv.Itoa(int(p.event.typ))) + } + p.skip() + return n +} + +func (p *parser) alias() *node { + n := p.node(aliasNode) + n.value = string(p.event.anchor) + p.skip() + return n +} + +func (p *parser) scalar() *node { + n := p.node(scalarNode) + n.value = string(p.event.value) + n.tag = string(p.event.tag) + n.implicit = p.event.implicit + p.anchor(n, p.event.anchor) + p.skip() + return n +} + +func (p *parser) sequence() *node { + n := p.node(sequenceNode) + p.anchor(n, p.event.anchor) + p.skip() + for p.event.typ != yaml_SEQUENCE_END_EVENT { + n.children = append(n.children, p.parse()) + } + p.skip() + return n +} + +func (p *parser) mapping() *node { + n := p.node(mappingNode) + p.anchor(n, p.event.anchor) + p.skip() + for p.event.typ != yaml_MAPPING_END_EVENT { + n.children = append(n.children, p.parse(), p.parse()) + } + p.skip() + return n +} + +// ---------------------------------------------------------------------------- +// Decoder, unmarshals a node into a provided value. + +type decoder struct { + doc *node + aliases map[string]bool + mapType reflect.Type + terrors []string +} + +var ( + mapItemType = reflect.TypeOf(MapItem{}) + durationType = reflect.TypeOf(time.Duration(0)) + defaultMapType = reflect.TypeOf(map[interface{}]interface{}{}) + ifaceType = defaultMapType.Elem() +) + +func newDecoder() *decoder { + d := &decoder{mapType: defaultMapType} + d.aliases = make(map[string]bool) + return d +} + +func (d *decoder) terror(n *node, tag string, out reflect.Value) { + if n.tag != "" { + tag = n.tag + } + value := n.value + if tag != yaml_SEQ_TAG && tag != yaml_MAP_TAG { + if len(value) > 10 { + value = " `" + value[:7] + "...`" + } else { + value = " `" + value + "`" + } + } + d.terrors = append(d.terrors, fmt.Sprintf("line %d: cannot unmarshal %s%s into %s", n.line+1, shortTag(tag), value, out.Type())) +} + +func (d *decoder) callUnmarshaler(n *node, u Unmarshaler) (good bool) { + terrlen := len(d.terrors) + err := u.UnmarshalYAML(func(v interface{}) (err error) { + defer handleErr(&err) + d.unmarshal(n, reflect.ValueOf(v)) + if len(d.terrors) > terrlen { + issues := d.terrors[terrlen:] + d.terrors = d.terrors[:terrlen] + return &TypeError{issues} + } + return nil + }) + if e, ok := err.(*TypeError); ok { + d.terrors = append(d.terrors, e.Errors...) + return false + } + if err != nil { + fail(err) + } + return true +} + +// d.prepare initializes and dereferences pointers and calls UnmarshalYAML +// if a value is found to implement it. +// It returns the initialized and dereferenced out value, whether +// unmarshalling was already done by UnmarshalYAML, and if so whether +// its types unmarshalled appropriately. +// +// If n holds a null value, prepare returns before doing anything. +func (d *decoder) prepare(n *node, out reflect.Value) (newout reflect.Value, unmarshaled, good bool) { + if n.tag == yaml_NULL_TAG || n.kind == scalarNode && n.tag == "" && (n.value == "null" || n.value == "" && n.implicit) { + return out, false, false + } + again := true + for again { + again = false + if out.Kind() == reflect.Ptr { + if out.IsNil() { + out.Set(reflect.New(out.Type().Elem())) + } + out = out.Elem() + again = true + } + if out.CanAddr() { + if u, ok := out.Addr().Interface().(Unmarshaler); ok { + good = d.callUnmarshaler(n, u) + return out, true, good + } + } + } + return out, false, false +} + +func (d *decoder) unmarshal(n *node, out reflect.Value) (good bool) { + switch n.kind { + case documentNode: + return d.document(n, out) + case aliasNode: + return d.alias(n, out) + } + out, unmarshaled, good := d.prepare(n, out) + if unmarshaled { + return good + } + switch n.kind { + case scalarNode: + good = d.scalar(n, out) + case mappingNode: + good = d.mapping(n, out) + case sequenceNode: + good = d.sequence(n, out) + default: + panic("internal error: unknown node kind: " + strconv.Itoa(n.kind)) + } + return good +} + +func (d *decoder) document(n *node, out reflect.Value) (good bool) { + if len(n.children) == 1 { + d.doc = n + d.unmarshal(n.children[0], out) + return true + } + return false +} + +func (d *decoder) alias(n *node, out reflect.Value) (good bool) { + an, ok := d.doc.anchors[n.value] + if !ok { + failf("unknown anchor '%s' referenced", n.value) + } + if d.aliases[n.value] { + failf("anchor '%s' value contains itself", n.value) + } + d.aliases[n.value] = true + good = d.unmarshal(an, out) + delete(d.aliases, n.value) + return good +} + +var zeroValue reflect.Value + +func resetMap(out reflect.Value) { + for _, k := range out.MapKeys() { + out.SetMapIndex(k, zeroValue) + } +} + +func (d *decoder) scalar(n *node, out reflect.Value) (good bool) { + var tag string + var resolved interface{} + if n.tag == "" && !n.implicit { + tag = yaml_STR_TAG + resolved = n.value + } else { + tag, resolved = resolve(n.tag, n.value) + if tag == yaml_BINARY_TAG { + data, err := base64.StdEncoding.DecodeString(resolved.(string)) + if err != nil { + failf("!!binary value contains invalid base64 data") + } + resolved = string(data) + } + } + if resolved == nil { + if out.Kind() == reflect.Map && !out.CanAddr() { + resetMap(out) + } else { + out.Set(reflect.Zero(out.Type())) + } + return true + } + if s, ok := resolved.(string); ok && out.CanAddr() { + if u, ok := out.Addr().Interface().(encoding.TextUnmarshaler); ok { + err := u.UnmarshalText([]byte(s)) + if err != nil { + fail(err) + } + return true + } + } + switch out.Kind() { + case reflect.String: + if tag == yaml_BINARY_TAG { + out.SetString(resolved.(string)) + good = true + } else if resolved != nil { + out.SetString(n.value) + good = true + } + case reflect.Interface: + if resolved == nil { + out.Set(reflect.Zero(out.Type())) + } else { + out.Set(reflect.ValueOf(resolved)) + } + good = true + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + switch resolved := resolved.(type) { + case int: + if !out.OverflowInt(int64(resolved)) { + out.SetInt(int64(resolved)) + good = true + } + case int64: + if !out.OverflowInt(resolved) { + out.SetInt(resolved) + good = true + } + case uint64: + if resolved <= math.MaxInt64 && !out.OverflowInt(int64(resolved)) { + out.SetInt(int64(resolved)) + good = true + } + case float64: + if resolved <= math.MaxInt64 && !out.OverflowInt(int64(resolved)) { + out.SetInt(int64(resolved)) + good = true + } + case string: + if out.Type() == durationType { + d, err := time.ParseDuration(resolved) + if err == nil { + out.SetInt(int64(d)) + good = true + } + } + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + switch resolved := resolved.(type) { + case int: + if resolved >= 0 && !out.OverflowUint(uint64(resolved)) { + out.SetUint(uint64(resolved)) + good = true + } + case int64: + if resolved >= 0 && !out.OverflowUint(uint64(resolved)) { + out.SetUint(uint64(resolved)) + good = true + } + case uint64: + if !out.OverflowUint(uint64(resolved)) { + out.SetUint(uint64(resolved)) + good = true + } + case float64: + if resolved <= math.MaxUint64 && !out.OverflowUint(uint64(resolved)) { + out.SetUint(uint64(resolved)) + good = true + } + } + case reflect.Bool: + switch resolved := resolved.(type) { + case bool: + out.SetBool(resolved) + good = true + } + case reflect.Float32, reflect.Float64: + switch resolved := resolved.(type) { + case int: + out.SetFloat(float64(resolved)) + good = true + case int64: + out.SetFloat(float64(resolved)) + good = true + case uint64: + out.SetFloat(float64(resolved)) + good = true + case float64: + out.SetFloat(resolved) + good = true + } + case reflect.Ptr: + if out.Type().Elem() == reflect.TypeOf(resolved) { + // TODO DOes this make sense? When is out a Ptr except when decoding a nil value? + elem := reflect.New(out.Type().Elem()) + elem.Elem().Set(reflect.ValueOf(resolved)) + out.Set(elem) + good = true + } + } + if !good { + d.terror(n, tag, out) + } + return good +} + +func settableValueOf(i interface{}) reflect.Value { + v := reflect.ValueOf(i) + sv := reflect.New(v.Type()).Elem() + sv.Set(v) + return sv +} + +func (d *decoder) sequence(n *node, out reflect.Value) (good bool) { + l := len(n.children) + + var iface reflect.Value + switch out.Kind() { + case reflect.Slice: + out.Set(reflect.MakeSlice(out.Type(), l, l)) + case reflect.Interface: + // No type hints. Will have to use a generic sequence. + iface = out + out = settableValueOf(make([]interface{}, l)) + default: + d.terror(n, yaml_SEQ_TAG, out) + return false + } + et := out.Type().Elem() + + j := 0 + for i := 0; i < l; i++ { + e := reflect.New(et).Elem() + if ok := d.unmarshal(n.children[i], e); ok { + out.Index(j).Set(e) + j++ + } + } + out.Set(out.Slice(0, j)) + if iface.IsValid() { + iface.Set(out) + } + return true +} + +func (d *decoder) mapping(n *node, out reflect.Value) (good bool) { + switch out.Kind() { + case reflect.Struct: + return d.mappingStruct(n, out) + case reflect.Slice: + return d.mappingSlice(n, out) + case reflect.Map: + // okay + case reflect.Interface: + if d.mapType.Kind() == reflect.Map { + iface := out + out = reflect.MakeMap(d.mapType) + iface.Set(out) + } else { + slicev := reflect.New(d.mapType).Elem() + if !d.mappingSlice(n, slicev) { + return false + } + out.Set(slicev) + return true + } + default: + d.terror(n, yaml_MAP_TAG, out) + return false + } + outt := out.Type() + kt := outt.Key() + et := outt.Elem() + + mapType := d.mapType + if outt.Key() == ifaceType && outt.Elem() == ifaceType { + d.mapType = outt + } + + if out.IsNil() { + out.Set(reflect.MakeMap(outt)) + } + l := len(n.children) + for i := 0; i < l; i += 2 { + if isMerge(n.children[i]) { + d.merge(n.children[i+1], out) + continue + } + k := reflect.New(kt).Elem() + if d.unmarshal(n.children[i], k) { + kkind := k.Kind() + if kkind == reflect.Interface { + kkind = k.Elem().Kind() + } + if kkind == reflect.Map || kkind == reflect.Slice { + failf("invalid map key: %#v", k.Interface()) + } + e := reflect.New(et).Elem() + if d.unmarshal(n.children[i+1], e) { + out.SetMapIndex(k, e) + } + } + } + d.mapType = mapType + return true +} + +func (d *decoder) mappingSlice(n *node, out reflect.Value) (good bool) { + outt := out.Type() + if outt.Elem() != mapItemType { + d.terror(n, yaml_MAP_TAG, out) + return false + } + + mapType := d.mapType + d.mapType = outt + + var slice []MapItem + var l = len(n.children) + for i := 0; i < l; i += 2 { + if isMerge(n.children[i]) { + d.merge(n.children[i+1], out) + continue + } + item := MapItem{} + k := reflect.ValueOf(&item.Key).Elem() + if d.unmarshal(n.children[i], k) { + v := reflect.ValueOf(&item.Value).Elem() + if d.unmarshal(n.children[i+1], v) { + slice = append(slice, item) + } + } + } + out.Set(reflect.ValueOf(slice)) + d.mapType = mapType + return true +} + +func (d *decoder) mappingStruct(n *node, out reflect.Value) (good bool) { + sinfo, err := getStructInfo(out.Type()) + if err != nil { + panic(err) + } + name := settableValueOf("") + l := len(n.children) + + var inlineMap reflect.Value + var elemType reflect.Type + if sinfo.InlineMap != -1 { + inlineMap = out.Field(sinfo.InlineMap) + inlineMap.Set(reflect.New(inlineMap.Type()).Elem()) + elemType = inlineMap.Type().Elem() + } + + for i := 0; i < l; i += 2 { + ni := n.children[i] + if isMerge(ni) { + d.merge(n.children[i+1], out) + continue + } + if !d.unmarshal(ni, name) { + continue + } + if info, ok := sinfo.FieldsMap[name.String()]; ok { + var field reflect.Value + if info.Inline == nil { + field = out.Field(info.Num) + } else { + field = out.FieldByIndex(info.Inline) + } + d.unmarshal(n.children[i+1], field) + } else if sinfo.InlineMap != -1 { + if inlineMap.IsNil() { + inlineMap.Set(reflect.MakeMap(inlineMap.Type())) + } + value := reflect.New(elemType).Elem() + d.unmarshal(n.children[i+1], value) + inlineMap.SetMapIndex(name, value) + } + } + return true +} + +func failWantMap() { + failf("map merge requires map or sequence of maps as the value") +} + +func (d *decoder) merge(n *node, out reflect.Value) { + switch n.kind { + case mappingNode: + d.unmarshal(n, out) + case aliasNode: + an, ok := d.doc.anchors[n.value] + if ok && an.kind != mappingNode { + failWantMap() + } + d.unmarshal(n, out) + case sequenceNode: + // Step backwards as earlier nodes take precedence. + for i := len(n.children) - 1; i >= 0; i-- { + ni := n.children[i] + if ni.kind == aliasNode { + an, ok := d.doc.anchors[ni.value] + if ok && an.kind != mappingNode { + failWantMap() + } + } else if ni.kind != mappingNode { + failWantMap() + } + d.unmarshal(ni, out) + } + default: + failWantMap() + } +} + +func isMerge(n *node) bool { + return n.kind == scalarNode && n.value == "<<" && (n.implicit == true || n.tag == yaml_MERGE_TAG) +} diff --git a/vendor/gopkg.in/yaml.v2/emitterc.go b/vendor/gopkg.in/yaml.v2/emitterc.go new file mode 100644 index 000000000..2befd553e --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/emitterc.go @@ -0,0 +1,1685 @@ +package yaml + +import ( + "bytes" +) + +// Flush the buffer if needed. +func flush(emitter *yaml_emitter_t) bool { + if emitter.buffer_pos+5 >= len(emitter.buffer) { + return yaml_emitter_flush(emitter) + } + return true +} + +// Put a character to the output buffer. +func put(emitter *yaml_emitter_t, value byte) bool { + if emitter.buffer_pos+5 >= len(emitter.buffer) && !yaml_emitter_flush(emitter) { + return false + } + emitter.buffer[emitter.buffer_pos] = value + emitter.buffer_pos++ + emitter.column++ + return true +} + +// Put a line break to the output buffer. +func put_break(emitter *yaml_emitter_t) bool { + if emitter.buffer_pos+5 >= len(emitter.buffer) && !yaml_emitter_flush(emitter) { + return false + } + switch emitter.line_break { + case yaml_CR_BREAK: + emitter.buffer[emitter.buffer_pos] = '\r' + emitter.buffer_pos += 1 + case yaml_LN_BREAK: + emitter.buffer[emitter.buffer_pos] = '\n' + emitter.buffer_pos += 1 + case yaml_CRLN_BREAK: + emitter.buffer[emitter.buffer_pos+0] = '\r' + emitter.buffer[emitter.buffer_pos+1] = '\n' + emitter.buffer_pos += 2 + default: + panic("unknown line break setting") + } + emitter.column = 0 + emitter.line++ + return true +} + +// Copy a character from a string into buffer. +func write(emitter *yaml_emitter_t, s []byte, i *int) bool { + if emitter.buffer_pos+5 >= len(emitter.buffer) && !yaml_emitter_flush(emitter) { + return false + } + p := emitter.buffer_pos + w := width(s[*i]) + switch w { + case 4: + emitter.buffer[p+3] = s[*i+3] + fallthrough + case 3: + emitter.buffer[p+2] = s[*i+2] + fallthrough + case 2: + emitter.buffer[p+1] = s[*i+1] + fallthrough + case 1: + emitter.buffer[p+0] = s[*i+0] + default: + panic("unknown character width") + } + emitter.column++ + emitter.buffer_pos += w + *i += w + return true +} + +// Write a whole string into buffer. +func write_all(emitter *yaml_emitter_t, s []byte) bool { + for i := 0; i < len(s); { + if !write(emitter, s, &i) { + return false + } + } + return true +} + +// Copy a line break character from a string into buffer. +func write_break(emitter *yaml_emitter_t, s []byte, i *int) bool { + if s[*i] == '\n' { + if !put_break(emitter) { + return false + } + *i++ + } else { + if !write(emitter, s, i) { + return false + } + emitter.column = 0 + emitter.line++ + } + return true +} + +// Set an emitter error and return false. +func yaml_emitter_set_emitter_error(emitter *yaml_emitter_t, problem string) bool { + emitter.error = yaml_EMITTER_ERROR + emitter.problem = problem + return false +} + +// Emit an event. +func yaml_emitter_emit(emitter *yaml_emitter_t, event *yaml_event_t) bool { + emitter.events = append(emitter.events, *event) + for !yaml_emitter_need_more_events(emitter) { + event := &emitter.events[emitter.events_head] + if !yaml_emitter_analyze_event(emitter, event) { + return false + } + if !yaml_emitter_state_machine(emitter, event) { + return false + } + yaml_event_delete(event) + emitter.events_head++ + } + return true +} + +// Check if we need to accumulate more events before emitting. +// +// We accumulate extra +// - 1 event for DOCUMENT-START +// - 2 events for SEQUENCE-START +// - 3 events for MAPPING-START +// +func yaml_emitter_need_more_events(emitter *yaml_emitter_t) bool { + if emitter.events_head == len(emitter.events) { + return true + } + var accumulate int + switch emitter.events[emitter.events_head].typ { + case yaml_DOCUMENT_START_EVENT: + accumulate = 1 + break + case yaml_SEQUENCE_START_EVENT: + accumulate = 2 + break + case yaml_MAPPING_START_EVENT: + accumulate = 3 + break + default: + return false + } + if len(emitter.events)-emitter.events_head > accumulate { + return false + } + var level int + for i := emitter.events_head; i < len(emitter.events); i++ { + switch emitter.events[i].typ { + case yaml_STREAM_START_EVENT, yaml_DOCUMENT_START_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT: + level++ + case yaml_STREAM_END_EVENT, yaml_DOCUMENT_END_EVENT, yaml_SEQUENCE_END_EVENT, yaml_MAPPING_END_EVENT: + level-- + } + if level == 0 { + return false + } + } + return true +} + +// Append a directive to the directives stack. +func yaml_emitter_append_tag_directive(emitter *yaml_emitter_t, value *yaml_tag_directive_t, allow_duplicates bool) bool { + for i := 0; i < len(emitter.tag_directives); i++ { + if bytes.Equal(value.handle, emitter.tag_directives[i].handle) { + if allow_duplicates { + return true + } + return yaml_emitter_set_emitter_error(emitter, "duplicate %TAG directive") + } + } + + // [Go] Do we actually need to copy this given garbage collection + // and the lack of deallocating destructors? + tag_copy := yaml_tag_directive_t{ + handle: make([]byte, len(value.handle)), + prefix: make([]byte, len(value.prefix)), + } + copy(tag_copy.handle, value.handle) + copy(tag_copy.prefix, value.prefix) + emitter.tag_directives = append(emitter.tag_directives, tag_copy) + return true +} + +// Increase the indentation level. +func yaml_emitter_increase_indent(emitter *yaml_emitter_t, flow, indentless bool) bool { + emitter.indents = append(emitter.indents, emitter.indent) + if emitter.indent < 0 { + if flow { + emitter.indent = emitter.best_indent + } else { + emitter.indent = 0 + } + } else if !indentless { + emitter.indent += emitter.best_indent + } + return true +} + +// State dispatcher. +func yaml_emitter_state_machine(emitter *yaml_emitter_t, event *yaml_event_t) bool { + switch emitter.state { + default: + case yaml_EMIT_STREAM_START_STATE: + return yaml_emitter_emit_stream_start(emitter, event) + + case yaml_EMIT_FIRST_DOCUMENT_START_STATE: + return yaml_emitter_emit_document_start(emitter, event, true) + + case yaml_EMIT_DOCUMENT_START_STATE: + return yaml_emitter_emit_document_start(emitter, event, false) + + case yaml_EMIT_DOCUMENT_CONTENT_STATE: + return yaml_emitter_emit_document_content(emitter, event) + + case yaml_EMIT_DOCUMENT_END_STATE: + return yaml_emitter_emit_document_end(emitter, event) + + case yaml_EMIT_FLOW_SEQUENCE_FIRST_ITEM_STATE: + return yaml_emitter_emit_flow_sequence_item(emitter, event, true) + + case yaml_EMIT_FLOW_SEQUENCE_ITEM_STATE: + return yaml_emitter_emit_flow_sequence_item(emitter, event, false) + + case yaml_EMIT_FLOW_MAPPING_FIRST_KEY_STATE: + return yaml_emitter_emit_flow_mapping_key(emitter, event, true) + + case yaml_EMIT_FLOW_MAPPING_KEY_STATE: + return yaml_emitter_emit_flow_mapping_key(emitter, event, false) + + case yaml_EMIT_FLOW_MAPPING_SIMPLE_VALUE_STATE: + return yaml_emitter_emit_flow_mapping_value(emitter, event, true) + + case yaml_EMIT_FLOW_MAPPING_VALUE_STATE: + return yaml_emitter_emit_flow_mapping_value(emitter, event, false) + + case yaml_EMIT_BLOCK_SEQUENCE_FIRST_ITEM_STATE: + return yaml_emitter_emit_block_sequence_item(emitter, event, true) + + case yaml_EMIT_BLOCK_SEQUENCE_ITEM_STATE: + return yaml_emitter_emit_block_sequence_item(emitter, event, false) + + case yaml_EMIT_BLOCK_MAPPING_FIRST_KEY_STATE: + return yaml_emitter_emit_block_mapping_key(emitter, event, true) + + case yaml_EMIT_BLOCK_MAPPING_KEY_STATE: + return yaml_emitter_emit_block_mapping_key(emitter, event, false) + + case yaml_EMIT_BLOCK_MAPPING_SIMPLE_VALUE_STATE: + return yaml_emitter_emit_block_mapping_value(emitter, event, true) + + case yaml_EMIT_BLOCK_MAPPING_VALUE_STATE: + return yaml_emitter_emit_block_mapping_value(emitter, event, false) + + case yaml_EMIT_END_STATE: + return yaml_emitter_set_emitter_error(emitter, "expected nothing after STREAM-END") + } + panic("invalid emitter state") +} + +// Expect STREAM-START. +func yaml_emitter_emit_stream_start(emitter *yaml_emitter_t, event *yaml_event_t) bool { + if event.typ != yaml_STREAM_START_EVENT { + return yaml_emitter_set_emitter_error(emitter, "expected STREAM-START") + } + if emitter.encoding == yaml_ANY_ENCODING { + emitter.encoding = event.encoding + if emitter.encoding == yaml_ANY_ENCODING { + emitter.encoding = yaml_UTF8_ENCODING + } + } + if emitter.best_indent < 2 || emitter.best_indent > 9 { + emitter.best_indent = 2 + } + if emitter.best_width >= 0 && emitter.best_width <= emitter.best_indent*2 { + emitter.best_width = 80 + } + if emitter.best_width < 0 { + emitter.best_width = 1<<31 - 1 + } + if emitter.line_break == yaml_ANY_BREAK { + emitter.line_break = yaml_LN_BREAK + } + + emitter.indent = -1 + emitter.line = 0 + emitter.column = 0 + emitter.whitespace = true + emitter.indention = true + + if emitter.encoding != yaml_UTF8_ENCODING { + if !yaml_emitter_write_bom(emitter) { + return false + } + } + emitter.state = yaml_EMIT_FIRST_DOCUMENT_START_STATE + return true +} + +// Expect DOCUMENT-START or STREAM-END. +func yaml_emitter_emit_document_start(emitter *yaml_emitter_t, event *yaml_event_t, first bool) bool { + + if event.typ == yaml_DOCUMENT_START_EVENT { + + if event.version_directive != nil { + if !yaml_emitter_analyze_version_directive(emitter, event.version_directive) { + return false + } + } + + for i := 0; i < len(event.tag_directives); i++ { + tag_directive := &event.tag_directives[i] + if !yaml_emitter_analyze_tag_directive(emitter, tag_directive) { + return false + } + if !yaml_emitter_append_tag_directive(emitter, tag_directive, false) { + return false + } + } + + for i := 0; i < len(default_tag_directives); i++ { + tag_directive := &default_tag_directives[i] + if !yaml_emitter_append_tag_directive(emitter, tag_directive, true) { + return false + } + } + + implicit := event.implicit + if !first || emitter.canonical { + implicit = false + } + + if emitter.open_ended && (event.version_directive != nil || len(event.tag_directives) > 0) { + if !yaml_emitter_write_indicator(emitter, []byte("..."), true, false, false) { + return false + } + if !yaml_emitter_write_indent(emitter) { + return false + } + } + + if event.version_directive != nil { + implicit = false + if !yaml_emitter_write_indicator(emitter, []byte("%YAML"), true, false, false) { + return false + } + if !yaml_emitter_write_indicator(emitter, []byte("1.1"), true, false, false) { + return false + } + if !yaml_emitter_write_indent(emitter) { + return false + } + } + + if len(event.tag_directives) > 0 { + implicit = false + for i := 0; i < len(event.tag_directives); i++ { + tag_directive := &event.tag_directives[i] + if !yaml_emitter_write_indicator(emitter, []byte("%TAG"), true, false, false) { + return false + } + if !yaml_emitter_write_tag_handle(emitter, tag_directive.handle) { + return false + } + if !yaml_emitter_write_tag_content(emitter, tag_directive.prefix, true) { + return false + } + if !yaml_emitter_write_indent(emitter) { + return false + } + } + } + + if yaml_emitter_check_empty_document(emitter) { + implicit = false + } + if !implicit { + if !yaml_emitter_write_indent(emitter) { + return false + } + if !yaml_emitter_write_indicator(emitter, []byte("---"), true, false, false) { + return false + } + if emitter.canonical { + if !yaml_emitter_write_indent(emitter) { + return false + } + } + } + + emitter.state = yaml_EMIT_DOCUMENT_CONTENT_STATE + return true + } + + if event.typ == yaml_STREAM_END_EVENT { + if emitter.open_ended { + if !yaml_emitter_write_indicator(emitter, []byte("..."), true, false, false) { + return false + } + if !yaml_emitter_write_indent(emitter) { + return false + } + } + if !yaml_emitter_flush(emitter) { + return false + } + emitter.state = yaml_EMIT_END_STATE + return true + } + + return yaml_emitter_set_emitter_error(emitter, "expected DOCUMENT-START or STREAM-END") +} + +// Expect the root node. +func yaml_emitter_emit_document_content(emitter *yaml_emitter_t, event *yaml_event_t) bool { + emitter.states = append(emitter.states, yaml_EMIT_DOCUMENT_END_STATE) + return yaml_emitter_emit_node(emitter, event, true, false, false, false) +} + +// Expect DOCUMENT-END. +func yaml_emitter_emit_document_end(emitter *yaml_emitter_t, event *yaml_event_t) bool { + if event.typ != yaml_DOCUMENT_END_EVENT { + return yaml_emitter_set_emitter_error(emitter, "expected DOCUMENT-END") + } + if !yaml_emitter_write_indent(emitter) { + return false + } + if !event.implicit { + // [Go] Allocate the slice elsewhere. + if !yaml_emitter_write_indicator(emitter, []byte("..."), true, false, false) { + return false + } + if !yaml_emitter_write_indent(emitter) { + return false + } + } + if !yaml_emitter_flush(emitter) { + return false + } + emitter.state = yaml_EMIT_DOCUMENT_START_STATE + emitter.tag_directives = emitter.tag_directives[:0] + return true +} + +// Expect a flow item node. +func yaml_emitter_emit_flow_sequence_item(emitter *yaml_emitter_t, event *yaml_event_t, first bool) bool { + if first { + if !yaml_emitter_write_indicator(emitter, []byte{'['}, true, true, false) { + return false + } + if !yaml_emitter_increase_indent(emitter, true, false) { + return false + } + emitter.flow_level++ + } + + if event.typ == yaml_SEQUENCE_END_EVENT { + emitter.flow_level-- + emitter.indent = emitter.indents[len(emitter.indents)-1] + emitter.indents = emitter.indents[:len(emitter.indents)-1] + if emitter.canonical && !first { + if !yaml_emitter_write_indicator(emitter, []byte{','}, false, false, false) { + return false + } + if !yaml_emitter_write_indent(emitter) { + return false + } + } + if !yaml_emitter_write_indicator(emitter, []byte{']'}, false, false, false) { + return false + } + emitter.state = emitter.states[len(emitter.states)-1] + emitter.states = emitter.states[:len(emitter.states)-1] + + return true + } + + if !first { + if !yaml_emitter_write_indicator(emitter, []byte{','}, false, false, false) { + return false + } + } + + if emitter.canonical || emitter.column > emitter.best_width { + if !yaml_emitter_write_indent(emitter) { + return false + } + } + emitter.states = append(emitter.states, yaml_EMIT_FLOW_SEQUENCE_ITEM_STATE) + return yaml_emitter_emit_node(emitter, event, false, true, false, false) +} + +// Expect a flow key node. +func yaml_emitter_emit_flow_mapping_key(emitter *yaml_emitter_t, event *yaml_event_t, first bool) bool { + if first { + if !yaml_emitter_write_indicator(emitter, []byte{'{'}, true, true, false) { + return false + } + if !yaml_emitter_increase_indent(emitter, true, false) { + return false + } + emitter.flow_level++ + } + + if event.typ == yaml_MAPPING_END_EVENT { + emitter.flow_level-- + emitter.indent = emitter.indents[len(emitter.indents)-1] + emitter.indents = emitter.indents[:len(emitter.indents)-1] + if emitter.canonical && !first { + if !yaml_emitter_write_indicator(emitter, []byte{','}, false, false, false) { + return false + } + if !yaml_emitter_write_indent(emitter) { + return false + } + } + if !yaml_emitter_write_indicator(emitter, []byte{'}'}, false, false, false) { + return false + } + emitter.state = emitter.states[len(emitter.states)-1] + emitter.states = emitter.states[:len(emitter.states)-1] + return true + } + + if !first { + if !yaml_emitter_write_indicator(emitter, []byte{','}, false, false, false) { + return false + } + } + if emitter.canonical || emitter.column > emitter.best_width { + if !yaml_emitter_write_indent(emitter) { + return false + } + } + + if !emitter.canonical && yaml_emitter_check_simple_key(emitter) { + emitter.states = append(emitter.states, yaml_EMIT_FLOW_MAPPING_SIMPLE_VALUE_STATE) + return yaml_emitter_emit_node(emitter, event, false, false, true, true) + } + if !yaml_emitter_write_indicator(emitter, []byte{'?'}, true, false, false) { + return false + } + emitter.states = append(emitter.states, yaml_EMIT_FLOW_MAPPING_VALUE_STATE) + return yaml_emitter_emit_node(emitter, event, false, false, true, false) +} + +// Expect a flow value node. +func yaml_emitter_emit_flow_mapping_value(emitter *yaml_emitter_t, event *yaml_event_t, simple bool) bool { + if simple { + if !yaml_emitter_write_indicator(emitter, []byte{':'}, false, false, false) { + return false + } + } else { + if emitter.canonical || emitter.column > emitter.best_width { + if !yaml_emitter_write_indent(emitter) { + return false + } + } + if !yaml_emitter_write_indicator(emitter, []byte{':'}, true, false, false) { + return false + } + } + emitter.states = append(emitter.states, yaml_EMIT_FLOW_MAPPING_KEY_STATE) + return yaml_emitter_emit_node(emitter, event, false, false, true, false) +} + +// Expect a block item node. +func yaml_emitter_emit_block_sequence_item(emitter *yaml_emitter_t, event *yaml_event_t, first bool) bool { + if first { + if !yaml_emitter_increase_indent(emitter, false, emitter.mapping_context && !emitter.indention) { + return false + } + } + if event.typ == yaml_SEQUENCE_END_EVENT { + emitter.indent = emitter.indents[len(emitter.indents)-1] + emitter.indents = emitter.indents[:len(emitter.indents)-1] + emitter.state = emitter.states[len(emitter.states)-1] + emitter.states = emitter.states[:len(emitter.states)-1] + return true + } + if !yaml_emitter_write_indent(emitter) { + return false + } + if !yaml_emitter_write_indicator(emitter, []byte{'-'}, true, false, true) { + return false + } + emitter.states = append(emitter.states, yaml_EMIT_BLOCK_SEQUENCE_ITEM_STATE) + return yaml_emitter_emit_node(emitter, event, false, true, false, false) +} + +// Expect a block key node. +func yaml_emitter_emit_block_mapping_key(emitter *yaml_emitter_t, event *yaml_event_t, first bool) bool { + if first { + if !yaml_emitter_increase_indent(emitter, false, false) { + return false + } + } + if event.typ == yaml_MAPPING_END_EVENT { + emitter.indent = emitter.indents[len(emitter.indents)-1] + emitter.indents = emitter.indents[:len(emitter.indents)-1] + emitter.state = emitter.states[len(emitter.states)-1] + emitter.states = emitter.states[:len(emitter.states)-1] + return true + } + if !yaml_emitter_write_indent(emitter) { + return false + } + if yaml_emitter_check_simple_key(emitter) { + emitter.states = append(emitter.states, yaml_EMIT_BLOCK_MAPPING_SIMPLE_VALUE_STATE) + return yaml_emitter_emit_node(emitter, event, false, false, true, true) + } + if !yaml_emitter_write_indicator(emitter, []byte{'?'}, true, false, true) { + return false + } + emitter.states = append(emitter.states, yaml_EMIT_BLOCK_MAPPING_VALUE_STATE) + return yaml_emitter_emit_node(emitter, event, false, false, true, false) +} + +// Expect a block value node. +func yaml_emitter_emit_block_mapping_value(emitter *yaml_emitter_t, event *yaml_event_t, simple bool) bool { + if simple { + if !yaml_emitter_write_indicator(emitter, []byte{':'}, false, false, false) { + return false + } + } else { + if !yaml_emitter_write_indent(emitter) { + return false + } + if !yaml_emitter_write_indicator(emitter, []byte{':'}, true, false, true) { + return false + } + } + emitter.states = append(emitter.states, yaml_EMIT_BLOCK_MAPPING_KEY_STATE) + return yaml_emitter_emit_node(emitter, event, false, false, true, false) +} + +// Expect a node. +func yaml_emitter_emit_node(emitter *yaml_emitter_t, event *yaml_event_t, + root bool, sequence bool, mapping bool, simple_key bool) bool { + + emitter.root_context = root + emitter.sequence_context = sequence + emitter.mapping_context = mapping + emitter.simple_key_context = simple_key + + switch event.typ { + case yaml_ALIAS_EVENT: + return yaml_emitter_emit_alias(emitter, event) + case yaml_SCALAR_EVENT: + return yaml_emitter_emit_scalar(emitter, event) + case yaml_SEQUENCE_START_EVENT: + return yaml_emitter_emit_sequence_start(emitter, event) + case yaml_MAPPING_START_EVENT: + return yaml_emitter_emit_mapping_start(emitter, event) + default: + return yaml_emitter_set_emitter_error(emitter, + "expected SCALAR, SEQUENCE-START, MAPPING-START, or ALIAS") + } + return false +} + +// Expect ALIAS. +func yaml_emitter_emit_alias(emitter *yaml_emitter_t, event *yaml_event_t) bool { + if !yaml_emitter_process_anchor(emitter) { + return false + } + emitter.state = emitter.states[len(emitter.states)-1] + emitter.states = emitter.states[:len(emitter.states)-1] + return true +} + +// Expect SCALAR. +func yaml_emitter_emit_scalar(emitter *yaml_emitter_t, event *yaml_event_t) bool { + if !yaml_emitter_select_scalar_style(emitter, event) { + return false + } + if !yaml_emitter_process_anchor(emitter) { + return false + } + if !yaml_emitter_process_tag(emitter) { + return false + } + if !yaml_emitter_increase_indent(emitter, true, false) { + return false + } + if !yaml_emitter_process_scalar(emitter) { + return false + } + emitter.indent = emitter.indents[len(emitter.indents)-1] + emitter.indents = emitter.indents[:len(emitter.indents)-1] + emitter.state = emitter.states[len(emitter.states)-1] + emitter.states = emitter.states[:len(emitter.states)-1] + return true +} + +// Expect SEQUENCE-START. +func yaml_emitter_emit_sequence_start(emitter *yaml_emitter_t, event *yaml_event_t) bool { + if !yaml_emitter_process_anchor(emitter) { + return false + } + if !yaml_emitter_process_tag(emitter) { + return false + } + if emitter.flow_level > 0 || emitter.canonical || event.sequence_style() == yaml_FLOW_SEQUENCE_STYLE || + yaml_emitter_check_empty_sequence(emitter) { + emitter.state = yaml_EMIT_FLOW_SEQUENCE_FIRST_ITEM_STATE + } else { + emitter.state = yaml_EMIT_BLOCK_SEQUENCE_FIRST_ITEM_STATE + } + return true +} + +// Expect MAPPING-START. +func yaml_emitter_emit_mapping_start(emitter *yaml_emitter_t, event *yaml_event_t) bool { + if !yaml_emitter_process_anchor(emitter) { + return false + } + if !yaml_emitter_process_tag(emitter) { + return false + } + if emitter.flow_level > 0 || emitter.canonical || event.mapping_style() == yaml_FLOW_MAPPING_STYLE || + yaml_emitter_check_empty_mapping(emitter) { + emitter.state = yaml_EMIT_FLOW_MAPPING_FIRST_KEY_STATE + } else { + emitter.state = yaml_EMIT_BLOCK_MAPPING_FIRST_KEY_STATE + } + return true +} + +// Check if the document content is an empty scalar. +func yaml_emitter_check_empty_document(emitter *yaml_emitter_t) bool { + return false // [Go] Huh? +} + +// Check if the next events represent an empty sequence. +func yaml_emitter_check_empty_sequence(emitter *yaml_emitter_t) bool { + if len(emitter.events)-emitter.events_head < 2 { + return false + } + return emitter.events[emitter.events_head].typ == yaml_SEQUENCE_START_EVENT && + emitter.events[emitter.events_head+1].typ == yaml_SEQUENCE_END_EVENT +} + +// Check if the next events represent an empty mapping. +func yaml_emitter_check_empty_mapping(emitter *yaml_emitter_t) bool { + if len(emitter.events)-emitter.events_head < 2 { + return false + } + return emitter.events[emitter.events_head].typ == yaml_MAPPING_START_EVENT && + emitter.events[emitter.events_head+1].typ == yaml_MAPPING_END_EVENT +} + +// Check if the next node can be expressed as a simple key. +func yaml_emitter_check_simple_key(emitter *yaml_emitter_t) bool { + length := 0 + switch emitter.events[emitter.events_head].typ { + case yaml_ALIAS_EVENT: + length += len(emitter.anchor_data.anchor) + case yaml_SCALAR_EVENT: + if emitter.scalar_data.multiline { + return false + } + length += len(emitter.anchor_data.anchor) + + len(emitter.tag_data.handle) + + len(emitter.tag_data.suffix) + + len(emitter.scalar_data.value) + case yaml_SEQUENCE_START_EVENT: + if !yaml_emitter_check_empty_sequence(emitter) { + return false + } + length += len(emitter.anchor_data.anchor) + + len(emitter.tag_data.handle) + + len(emitter.tag_data.suffix) + case yaml_MAPPING_START_EVENT: + if !yaml_emitter_check_empty_mapping(emitter) { + return false + } + length += len(emitter.anchor_data.anchor) + + len(emitter.tag_data.handle) + + len(emitter.tag_data.suffix) + default: + return false + } + return length <= 128 +} + +// Determine an acceptable scalar style. +func yaml_emitter_select_scalar_style(emitter *yaml_emitter_t, event *yaml_event_t) bool { + + no_tag := len(emitter.tag_data.handle) == 0 && len(emitter.tag_data.suffix) == 0 + if no_tag && !event.implicit && !event.quoted_implicit { + return yaml_emitter_set_emitter_error(emitter, "neither tag nor implicit flags are specified") + } + + style := event.scalar_style() + if style == yaml_ANY_SCALAR_STYLE { + style = yaml_PLAIN_SCALAR_STYLE + } + if emitter.canonical { + style = yaml_DOUBLE_QUOTED_SCALAR_STYLE + } + if emitter.simple_key_context && emitter.scalar_data.multiline { + style = yaml_DOUBLE_QUOTED_SCALAR_STYLE + } + + if style == yaml_PLAIN_SCALAR_STYLE { + if emitter.flow_level > 0 && !emitter.scalar_data.flow_plain_allowed || + emitter.flow_level == 0 && !emitter.scalar_data.block_plain_allowed { + style = yaml_SINGLE_QUOTED_SCALAR_STYLE + } + if len(emitter.scalar_data.value) == 0 && (emitter.flow_level > 0 || emitter.simple_key_context) { + style = yaml_SINGLE_QUOTED_SCALAR_STYLE + } + if no_tag && !event.implicit { + style = yaml_SINGLE_QUOTED_SCALAR_STYLE + } + } + if style == yaml_SINGLE_QUOTED_SCALAR_STYLE { + if !emitter.scalar_data.single_quoted_allowed { + style = yaml_DOUBLE_QUOTED_SCALAR_STYLE + } + } + if style == yaml_LITERAL_SCALAR_STYLE || style == yaml_FOLDED_SCALAR_STYLE { + if !emitter.scalar_data.block_allowed || emitter.flow_level > 0 || emitter.simple_key_context { + style = yaml_DOUBLE_QUOTED_SCALAR_STYLE + } + } + + if no_tag && !event.quoted_implicit && style != yaml_PLAIN_SCALAR_STYLE { + emitter.tag_data.handle = []byte{'!'} + } + emitter.scalar_data.style = style + return true +} + +// Write an achor. +func yaml_emitter_process_anchor(emitter *yaml_emitter_t) bool { + if emitter.anchor_data.anchor == nil { + return true + } + c := []byte{'&'} + if emitter.anchor_data.alias { + c[0] = '*' + } + if !yaml_emitter_write_indicator(emitter, c, true, false, false) { + return false + } + return yaml_emitter_write_anchor(emitter, emitter.anchor_data.anchor) +} + +// Write a tag. +func yaml_emitter_process_tag(emitter *yaml_emitter_t) bool { + if len(emitter.tag_data.handle) == 0 && len(emitter.tag_data.suffix) == 0 { + return true + } + if len(emitter.tag_data.handle) > 0 { + if !yaml_emitter_write_tag_handle(emitter, emitter.tag_data.handle) { + return false + } + if len(emitter.tag_data.suffix) > 0 { + if !yaml_emitter_write_tag_content(emitter, emitter.tag_data.suffix, false) { + return false + } + } + } else { + // [Go] Allocate these slices elsewhere. + if !yaml_emitter_write_indicator(emitter, []byte("!<"), true, false, false) { + return false + } + if !yaml_emitter_write_tag_content(emitter, emitter.tag_data.suffix, false) { + return false + } + if !yaml_emitter_write_indicator(emitter, []byte{'>'}, false, false, false) { + return false + } + } + return true +} + +// Write a scalar. +func yaml_emitter_process_scalar(emitter *yaml_emitter_t) bool { + switch emitter.scalar_data.style { + case yaml_PLAIN_SCALAR_STYLE: + return yaml_emitter_write_plain_scalar(emitter, emitter.scalar_data.value, !emitter.simple_key_context) + + case yaml_SINGLE_QUOTED_SCALAR_STYLE: + return yaml_emitter_write_single_quoted_scalar(emitter, emitter.scalar_data.value, !emitter.simple_key_context) + + case yaml_DOUBLE_QUOTED_SCALAR_STYLE: + return yaml_emitter_write_double_quoted_scalar(emitter, emitter.scalar_data.value, !emitter.simple_key_context) + + case yaml_LITERAL_SCALAR_STYLE: + return yaml_emitter_write_literal_scalar(emitter, emitter.scalar_data.value) + + case yaml_FOLDED_SCALAR_STYLE: + return yaml_emitter_write_folded_scalar(emitter, emitter.scalar_data.value) + } + panic("unknown scalar style") +} + +// Check if a %YAML directive is valid. +func yaml_emitter_analyze_version_directive(emitter *yaml_emitter_t, version_directive *yaml_version_directive_t) bool { + if version_directive.major != 1 || version_directive.minor != 1 { + return yaml_emitter_set_emitter_error(emitter, "incompatible %YAML directive") + } + return true +} + +// Check if a %TAG directive is valid. +func yaml_emitter_analyze_tag_directive(emitter *yaml_emitter_t, tag_directive *yaml_tag_directive_t) bool { + handle := tag_directive.handle + prefix := tag_directive.prefix + if len(handle) == 0 { + return yaml_emitter_set_emitter_error(emitter, "tag handle must not be empty") + } + if handle[0] != '!' { + return yaml_emitter_set_emitter_error(emitter, "tag handle must start with '!'") + } + if handle[len(handle)-1] != '!' { + return yaml_emitter_set_emitter_error(emitter, "tag handle must end with '!'") + } + for i := 1; i < len(handle)-1; i += width(handle[i]) { + if !is_alpha(handle, i) { + return yaml_emitter_set_emitter_error(emitter, "tag handle must contain alphanumerical characters only") + } + } + if len(prefix) == 0 { + return yaml_emitter_set_emitter_error(emitter, "tag prefix must not be empty") + } + return true +} + +// Check if an anchor is valid. +func yaml_emitter_analyze_anchor(emitter *yaml_emitter_t, anchor []byte, alias bool) bool { + if len(anchor) == 0 { + problem := "anchor value must not be empty" + if alias { + problem = "alias value must not be empty" + } + return yaml_emitter_set_emitter_error(emitter, problem) + } + for i := 0; i < len(anchor); i += width(anchor[i]) { + if !is_alpha(anchor, i) { + problem := "anchor value must contain alphanumerical characters only" + if alias { + problem = "alias value must contain alphanumerical characters only" + } + return yaml_emitter_set_emitter_error(emitter, problem) + } + } + emitter.anchor_data.anchor = anchor + emitter.anchor_data.alias = alias + return true +} + +// Check if a tag is valid. +func yaml_emitter_analyze_tag(emitter *yaml_emitter_t, tag []byte) bool { + if len(tag) == 0 { + return yaml_emitter_set_emitter_error(emitter, "tag value must not be empty") + } + for i := 0; i < len(emitter.tag_directives); i++ { + tag_directive := &emitter.tag_directives[i] + if bytes.HasPrefix(tag, tag_directive.prefix) { + emitter.tag_data.handle = tag_directive.handle + emitter.tag_data.suffix = tag[len(tag_directive.prefix):] + return true + } + } + emitter.tag_data.suffix = tag + return true +} + +// Check if a scalar is valid. +func yaml_emitter_analyze_scalar(emitter *yaml_emitter_t, value []byte) bool { + var ( + block_indicators = false + flow_indicators = false + line_breaks = false + special_characters = false + + leading_space = false + leading_break = false + trailing_space = false + trailing_break = false + break_space = false + space_break = false + + preceeded_by_whitespace = false + followed_by_whitespace = false + previous_space = false + previous_break = false + ) + + emitter.scalar_data.value = value + + if len(value) == 0 { + emitter.scalar_data.multiline = false + emitter.scalar_data.flow_plain_allowed = false + emitter.scalar_data.block_plain_allowed = true + emitter.scalar_data.single_quoted_allowed = true + emitter.scalar_data.block_allowed = false + return true + } + + if len(value) >= 3 && ((value[0] == '-' && value[1] == '-' && value[2] == '-') || (value[0] == '.' && value[1] == '.' && value[2] == '.')) { + block_indicators = true + flow_indicators = true + } + + preceeded_by_whitespace = true + for i, w := 0, 0; i < len(value); i += w { + w = width(value[i]) + followed_by_whitespace = i+w >= len(value) || is_blank(value, i+w) + + if i == 0 { + switch value[i] { + case '#', ',', '[', ']', '{', '}', '&', '*', '!', '|', '>', '\'', '"', '%', '@', '`': + flow_indicators = true + block_indicators = true + case '?', ':': + flow_indicators = true + if followed_by_whitespace { + block_indicators = true + } + case '-': + if followed_by_whitespace { + flow_indicators = true + block_indicators = true + } + } + } else { + switch value[i] { + case ',', '?', '[', ']', '{', '}': + flow_indicators = true + case ':': + flow_indicators = true + if followed_by_whitespace { + block_indicators = true + } + case '#': + if preceeded_by_whitespace { + flow_indicators = true + block_indicators = true + } + } + } + + if !is_printable(value, i) || !is_ascii(value, i) && !emitter.unicode { + special_characters = true + } + if is_space(value, i) { + if i == 0 { + leading_space = true + } + if i+width(value[i]) == len(value) { + trailing_space = true + } + if previous_break { + break_space = true + } + previous_space = true + previous_break = false + } else if is_break(value, i) { + line_breaks = true + if i == 0 { + leading_break = true + } + if i+width(value[i]) == len(value) { + trailing_break = true + } + if previous_space { + space_break = true + } + previous_space = false + previous_break = true + } else { + previous_space = false + previous_break = false + } + + // [Go]: Why 'z'? Couldn't be the end of the string as that's the loop condition. + preceeded_by_whitespace = is_blankz(value, i) + } + + emitter.scalar_data.multiline = line_breaks + emitter.scalar_data.flow_plain_allowed = true + emitter.scalar_data.block_plain_allowed = true + emitter.scalar_data.single_quoted_allowed = true + emitter.scalar_data.block_allowed = true + + if leading_space || leading_break || trailing_space || trailing_break { + emitter.scalar_data.flow_plain_allowed = false + emitter.scalar_data.block_plain_allowed = false + } + if trailing_space { + emitter.scalar_data.block_allowed = false + } + if break_space { + emitter.scalar_data.flow_plain_allowed = false + emitter.scalar_data.block_plain_allowed = false + emitter.scalar_data.single_quoted_allowed = false + } + if space_break || special_characters { + emitter.scalar_data.flow_plain_allowed = false + emitter.scalar_data.block_plain_allowed = false + emitter.scalar_data.single_quoted_allowed = false + emitter.scalar_data.block_allowed = false + } + if line_breaks { + emitter.scalar_data.flow_plain_allowed = false + emitter.scalar_data.block_plain_allowed = false + } + if flow_indicators { + emitter.scalar_data.flow_plain_allowed = false + } + if block_indicators { + emitter.scalar_data.block_plain_allowed = false + } + return true +} + +// Check if the event data is valid. +func yaml_emitter_analyze_event(emitter *yaml_emitter_t, event *yaml_event_t) bool { + + emitter.anchor_data.anchor = nil + emitter.tag_data.handle = nil + emitter.tag_data.suffix = nil + emitter.scalar_data.value = nil + + switch event.typ { + case yaml_ALIAS_EVENT: + if !yaml_emitter_analyze_anchor(emitter, event.anchor, true) { + return false + } + + case yaml_SCALAR_EVENT: + if len(event.anchor) > 0 { + if !yaml_emitter_analyze_anchor(emitter, event.anchor, false) { + return false + } + } + if len(event.tag) > 0 && (emitter.canonical || (!event.implicit && !event.quoted_implicit)) { + if !yaml_emitter_analyze_tag(emitter, event.tag) { + return false + } + } + if !yaml_emitter_analyze_scalar(emitter, event.value) { + return false + } + + case yaml_SEQUENCE_START_EVENT: + if len(event.anchor) > 0 { + if !yaml_emitter_analyze_anchor(emitter, event.anchor, false) { + return false + } + } + if len(event.tag) > 0 && (emitter.canonical || !event.implicit) { + if !yaml_emitter_analyze_tag(emitter, event.tag) { + return false + } + } + + case yaml_MAPPING_START_EVENT: + if len(event.anchor) > 0 { + if !yaml_emitter_analyze_anchor(emitter, event.anchor, false) { + return false + } + } + if len(event.tag) > 0 && (emitter.canonical || !event.implicit) { + if !yaml_emitter_analyze_tag(emitter, event.tag) { + return false + } + } + } + return true +} + +// Write the BOM character. +func yaml_emitter_write_bom(emitter *yaml_emitter_t) bool { + if !flush(emitter) { + return false + } + pos := emitter.buffer_pos + emitter.buffer[pos+0] = '\xEF' + emitter.buffer[pos+1] = '\xBB' + emitter.buffer[pos+2] = '\xBF' + emitter.buffer_pos += 3 + return true +} + +func yaml_emitter_write_indent(emitter *yaml_emitter_t) bool { + indent := emitter.indent + if indent < 0 { + indent = 0 + } + if !emitter.indention || emitter.column > indent || (emitter.column == indent && !emitter.whitespace) { + if !put_break(emitter) { + return false + } + } + for emitter.column < indent { + if !put(emitter, ' ') { + return false + } + } + emitter.whitespace = true + emitter.indention = true + return true +} + +func yaml_emitter_write_indicator(emitter *yaml_emitter_t, indicator []byte, need_whitespace, is_whitespace, is_indention bool) bool { + if need_whitespace && !emitter.whitespace { + if !put(emitter, ' ') { + return false + } + } + if !write_all(emitter, indicator) { + return false + } + emitter.whitespace = is_whitespace + emitter.indention = (emitter.indention && is_indention) + emitter.open_ended = false + return true +} + +func yaml_emitter_write_anchor(emitter *yaml_emitter_t, value []byte) bool { + if !write_all(emitter, value) { + return false + } + emitter.whitespace = false + emitter.indention = false + return true +} + +func yaml_emitter_write_tag_handle(emitter *yaml_emitter_t, value []byte) bool { + if !emitter.whitespace { + if !put(emitter, ' ') { + return false + } + } + if !write_all(emitter, value) { + return false + } + emitter.whitespace = false + emitter.indention = false + return true +} + +func yaml_emitter_write_tag_content(emitter *yaml_emitter_t, value []byte, need_whitespace bool) bool { + if need_whitespace && !emitter.whitespace { + if !put(emitter, ' ') { + return false + } + } + for i := 0; i < len(value); { + var must_write bool + switch value[i] { + case ';', '/', '?', ':', '@', '&', '=', '+', '$', ',', '_', '.', '~', '*', '\'', '(', ')', '[', ']': + must_write = true + default: + must_write = is_alpha(value, i) + } + if must_write { + if !write(emitter, value, &i) { + return false + } + } else { + w := width(value[i]) + for k := 0; k < w; k++ { + octet := value[i] + i++ + if !put(emitter, '%') { + return false + } + + c := octet >> 4 + if c < 10 { + c += '0' + } else { + c += 'A' - 10 + } + if !put(emitter, c) { + return false + } + + c = octet & 0x0f + if c < 10 { + c += '0' + } else { + c += 'A' - 10 + } + if !put(emitter, c) { + return false + } + } + } + } + emitter.whitespace = false + emitter.indention = false + return true +} + +func yaml_emitter_write_plain_scalar(emitter *yaml_emitter_t, value []byte, allow_breaks bool) bool { + if !emitter.whitespace { + if !put(emitter, ' ') { + return false + } + } + + spaces := false + breaks := false + for i := 0; i < len(value); { + if is_space(value, i) { + if allow_breaks && !spaces && emitter.column > emitter.best_width && !is_space(value, i+1) { + if !yaml_emitter_write_indent(emitter) { + return false + } + i += width(value[i]) + } else { + if !write(emitter, value, &i) { + return false + } + } + spaces = true + } else if is_break(value, i) { + if !breaks && value[i] == '\n' { + if !put_break(emitter) { + return false + } + } + if !write_break(emitter, value, &i) { + return false + } + emitter.indention = true + breaks = true + } else { + if breaks { + if !yaml_emitter_write_indent(emitter) { + return false + } + } + if !write(emitter, value, &i) { + return false + } + emitter.indention = false + spaces = false + breaks = false + } + } + + emitter.whitespace = false + emitter.indention = false + if emitter.root_context { + emitter.open_ended = true + } + + return true +} + +func yaml_emitter_write_single_quoted_scalar(emitter *yaml_emitter_t, value []byte, allow_breaks bool) bool { + + if !yaml_emitter_write_indicator(emitter, []byte{'\''}, true, false, false) { + return false + } + + spaces := false + breaks := false + for i := 0; i < len(value); { + if is_space(value, i) { + if allow_breaks && !spaces && emitter.column > emitter.best_width && i > 0 && i < len(value)-1 && !is_space(value, i+1) { + if !yaml_emitter_write_indent(emitter) { + return false + } + i += width(value[i]) + } else { + if !write(emitter, value, &i) { + return false + } + } + spaces = true + } else if is_break(value, i) { + if !breaks && value[i] == '\n' { + if !put_break(emitter) { + return false + } + } + if !write_break(emitter, value, &i) { + return false + } + emitter.indention = true + breaks = true + } else { + if breaks { + if !yaml_emitter_write_indent(emitter) { + return false + } + } + if value[i] == '\'' { + if !put(emitter, '\'') { + return false + } + } + if !write(emitter, value, &i) { + return false + } + emitter.indention = false + spaces = false + breaks = false + } + } + if !yaml_emitter_write_indicator(emitter, []byte{'\''}, false, false, false) { + return false + } + emitter.whitespace = false + emitter.indention = false + return true +} + +func yaml_emitter_write_double_quoted_scalar(emitter *yaml_emitter_t, value []byte, allow_breaks bool) bool { + spaces := false + if !yaml_emitter_write_indicator(emitter, []byte{'"'}, true, false, false) { + return false + } + + for i := 0; i < len(value); { + if !is_printable(value, i) || (!emitter.unicode && !is_ascii(value, i)) || + is_bom(value, i) || is_break(value, i) || + value[i] == '"' || value[i] == '\\' { + + octet := value[i] + + var w int + var v rune + switch { + case octet&0x80 == 0x00: + w, v = 1, rune(octet&0x7F) + case octet&0xE0 == 0xC0: + w, v = 2, rune(octet&0x1F) + case octet&0xF0 == 0xE0: + w, v = 3, rune(octet&0x0F) + case octet&0xF8 == 0xF0: + w, v = 4, rune(octet&0x07) + } + for k := 1; k < w; k++ { + octet = value[i+k] + v = (v << 6) + (rune(octet) & 0x3F) + } + i += w + + if !put(emitter, '\\') { + return false + } + + var ok bool + switch v { + case 0x00: + ok = put(emitter, '0') + case 0x07: + ok = put(emitter, 'a') + case 0x08: + ok = put(emitter, 'b') + case 0x09: + ok = put(emitter, 't') + case 0x0A: + ok = put(emitter, 'n') + case 0x0b: + ok = put(emitter, 'v') + case 0x0c: + ok = put(emitter, 'f') + case 0x0d: + ok = put(emitter, 'r') + case 0x1b: + ok = put(emitter, 'e') + case 0x22: + ok = put(emitter, '"') + case 0x5c: + ok = put(emitter, '\\') + case 0x85: + ok = put(emitter, 'N') + case 0xA0: + ok = put(emitter, '_') + case 0x2028: + ok = put(emitter, 'L') + case 0x2029: + ok = put(emitter, 'P') + default: + if v <= 0xFF { + ok = put(emitter, 'x') + w = 2 + } else if v <= 0xFFFF { + ok = put(emitter, 'u') + w = 4 + } else { + ok = put(emitter, 'U') + w = 8 + } + for k := (w - 1) * 4; ok && k >= 0; k -= 4 { + digit := byte((v >> uint(k)) & 0x0F) + if digit < 10 { + ok = put(emitter, digit+'0') + } else { + ok = put(emitter, digit+'A'-10) + } + } + } + if !ok { + return false + } + spaces = false + } else if is_space(value, i) { + if allow_breaks && !spaces && emitter.column > emitter.best_width && i > 0 && i < len(value)-1 { + if !yaml_emitter_write_indent(emitter) { + return false + } + if is_space(value, i+1) { + if !put(emitter, '\\') { + return false + } + } + i += width(value[i]) + } else if !write(emitter, value, &i) { + return false + } + spaces = true + } else { + if !write(emitter, value, &i) { + return false + } + spaces = false + } + } + if !yaml_emitter_write_indicator(emitter, []byte{'"'}, false, false, false) { + return false + } + emitter.whitespace = false + emitter.indention = false + return true +} + +func yaml_emitter_write_block_scalar_hints(emitter *yaml_emitter_t, value []byte) bool { + if is_space(value, 0) || is_break(value, 0) { + indent_hint := []byte{'0' + byte(emitter.best_indent)} + if !yaml_emitter_write_indicator(emitter, indent_hint, false, false, false) { + return false + } + } + + emitter.open_ended = false + + var chomp_hint [1]byte + if len(value) == 0 { + chomp_hint[0] = '-' + } else { + i := len(value) - 1 + for value[i]&0xC0 == 0x80 { + i-- + } + if !is_break(value, i) { + chomp_hint[0] = '-' + } else if i == 0 { + chomp_hint[0] = '+' + emitter.open_ended = true + } else { + i-- + for value[i]&0xC0 == 0x80 { + i-- + } + if is_break(value, i) { + chomp_hint[0] = '+' + emitter.open_ended = true + } + } + } + if chomp_hint[0] != 0 { + if !yaml_emitter_write_indicator(emitter, chomp_hint[:], false, false, false) { + return false + } + } + return true +} + +func yaml_emitter_write_literal_scalar(emitter *yaml_emitter_t, value []byte) bool { + if !yaml_emitter_write_indicator(emitter, []byte{'|'}, true, false, false) { + return false + } + if !yaml_emitter_write_block_scalar_hints(emitter, value) { + return false + } + if !put_break(emitter) { + return false + } + emitter.indention = true + emitter.whitespace = true + breaks := true + for i := 0; i < len(value); { + if is_break(value, i) { + if !write_break(emitter, value, &i) { + return false + } + emitter.indention = true + breaks = true + } else { + if breaks { + if !yaml_emitter_write_indent(emitter) { + return false + } + } + if !write(emitter, value, &i) { + return false + } + emitter.indention = false + breaks = false + } + } + + return true +} + +func yaml_emitter_write_folded_scalar(emitter *yaml_emitter_t, value []byte) bool { + if !yaml_emitter_write_indicator(emitter, []byte{'>'}, true, false, false) { + return false + } + if !yaml_emitter_write_block_scalar_hints(emitter, value) { + return false + } + + if !put_break(emitter) { + return false + } + emitter.indention = true + emitter.whitespace = true + + breaks := true + leading_spaces := true + for i := 0; i < len(value); { + if is_break(value, i) { + if !breaks && !leading_spaces && value[i] == '\n' { + k := 0 + for is_break(value, k) { + k += width(value[k]) + } + if !is_blankz(value, k) { + if !put_break(emitter) { + return false + } + } + } + if !write_break(emitter, value, &i) { + return false + } + emitter.indention = true + breaks = true + } else { + if breaks { + if !yaml_emitter_write_indent(emitter) { + return false + } + leading_spaces = is_blank(value, i) + } + if !breaks && is_space(value, i) && !is_space(value, i+1) && emitter.column > emitter.best_width { + if !yaml_emitter_write_indent(emitter) { + return false + } + i += width(value[i]) + } else { + if !write(emitter, value, &i) { + return false + } + } + emitter.indention = false + breaks = false + } + } + return true +} diff --git a/vendor/gopkg.in/yaml.v2/encode.go b/vendor/gopkg.in/yaml.v2/encode.go new file mode 100644 index 000000000..84f849955 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/encode.go @@ -0,0 +1,306 @@ +package yaml + +import ( + "encoding" + "fmt" + "reflect" + "regexp" + "sort" + "strconv" + "strings" + "time" +) + +type encoder struct { + emitter yaml_emitter_t + event yaml_event_t + out []byte + flow bool +} + +func newEncoder() (e *encoder) { + e = &encoder{} + e.must(yaml_emitter_initialize(&e.emitter)) + yaml_emitter_set_output_string(&e.emitter, &e.out) + yaml_emitter_set_unicode(&e.emitter, true) + e.must(yaml_stream_start_event_initialize(&e.event, yaml_UTF8_ENCODING)) + e.emit() + e.must(yaml_document_start_event_initialize(&e.event, nil, nil, true)) + e.emit() + return e +} + +func (e *encoder) finish() { + e.must(yaml_document_end_event_initialize(&e.event, true)) + e.emit() + e.emitter.open_ended = false + e.must(yaml_stream_end_event_initialize(&e.event)) + e.emit() +} + +func (e *encoder) destroy() { + yaml_emitter_delete(&e.emitter) +} + +func (e *encoder) emit() { + // This will internally delete the e.event value. + if !yaml_emitter_emit(&e.emitter, &e.event) && e.event.typ != yaml_DOCUMENT_END_EVENT && e.event.typ != yaml_STREAM_END_EVENT { + e.must(false) + } +} + +func (e *encoder) must(ok bool) { + if !ok { + msg := e.emitter.problem + if msg == "" { + msg = "unknown problem generating YAML content" + } + failf("%s", msg) + } +} + +func (e *encoder) marshal(tag string, in reflect.Value) { + if !in.IsValid() { + e.nilv() + return + } + iface := in.Interface() + if m, ok := iface.(Marshaler); ok { + v, err := m.MarshalYAML() + if err != nil { + fail(err) + } + if v == nil { + e.nilv() + return + } + in = reflect.ValueOf(v) + } else if m, ok := iface.(encoding.TextMarshaler); ok { + text, err := m.MarshalText() + if err != nil { + fail(err) + } + in = reflect.ValueOf(string(text)) + } + switch in.Kind() { + case reflect.Interface: + if in.IsNil() { + e.nilv() + } else { + e.marshal(tag, in.Elem()) + } + case reflect.Map: + e.mapv(tag, in) + case reflect.Ptr: + if in.IsNil() { + e.nilv() + } else { + e.marshal(tag, in.Elem()) + } + case reflect.Struct: + e.structv(tag, in) + case reflect.Slice: + if in.Type().Elem() == mapItemType { + e.itemsv(tag, in) + } else { + e.slicev(tag, in) + } + case reflect.String: + e.stringv(tag, in) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + if in.Type() == durationType { + e.stringv(tag, reflect.ValueOf(iface.(time.Duration).String())) + } else { + e.intv(tag, in) + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + e.uintv(tag, in) + case reflect.Float32, reflect.Float64: + e.floatv(tag, in) + case reflect.Bool: + e.boolv(tag, in) + default: + panic("cannot marshal type: " + in.Type().String()) + } +} + +func (e *encoder) mapv(tag string, in reflect.Value) { + e.mappingv(tag, func() { + keys := keyList(in.MapKeys()) + sort.Sort(keys) + for _, k := range keys { + e.marshal("", k) + e.marshal("", in.MapIndex(k)) + } + }) +} + +func (e *encoder) itemsv(tag string, in reflect.Value) { + e.mappingv(tag, func() { + slice := in.Convert(reflect.TypeOf([]MapItem{})).Interface().([]MapItem) + for _, item := range slice { + e.marshal("", reflect.ValueOf(item.Key)) + e.marshal("", reflect.ValueOf(item.Value)) + } + }) +} + +func (e *encoder) structv(tag string, in reflect.Value) { + sinfo, err := getStructInfo(in.Type()) + if err != nil { + panic(err) + } + e.mappingv(tag, func() { + for _, info := range sinfo.FieldsList { + var value reflect.Value + if info.Inline == nil { + value = in.Field(info.Num) + } else { + value = in.FieldByIndex(info.Inline) + } + if info.OmitEmpty && isZero(value) { + continue + } + e.marshal("", reflect.ValueOf(info.Key)) + e.flow = info.Flow + e.marshal("", value) + } + if sinfo.InlineMap >= 0 { + m := in.Field(sinfo.InlineMap) + if m.Len() > 0 { + e.flow = false + keys := keyList(m.MapKeys()) + sort.Sort(keys) + for _, k := range keys { + if _, found := sinfo.FieldsMap[k.String()]; found { + panic(fmt.Sprintf("Can't have key %q in inlined map; conflicts with struct field", k.String())) + } + e.marshal("", k) + e.flow = false + e.marshal("", m.MapIndex(k)) + } + } + } + }) +} + +func (e *encoder) mappingv(tag string, f func()) { + implicit := tag == "" + style := yaml_BLOCK_MAPPING_STYLE + if e.flow { + e.flow = false + style = yaml_FLOW_MAPPING_STYLE + } + e.must(yaml_mapping_start_event_initialize(&e.event, nil, []byte(tag), implicit, style)) + e.emit() + f() + e.must(yaml_mapping_end_event_initialize(&e.event)) + e.emit() +} + +func (e *encoder) slicev(tag string, in reflect.Value) { + implicit := tag == "" + style := yaml_BLOCK_SEQUENCE_STYLE + if e.flow { + e.flow = false + style = yaml_FLOW_SEQUENCE_STYLE + } + e.must(yaml_sequence_start_event_initialize(&e.event, nil, []byte(tag), implicit, style)) + e.emit() + n := in.Len() + for i := 0; i < n; i++ { + e.marshal("", in.Index(i)) + } + e.must(yaml_sequence_end_event_initialize(&e.event)) + e.emit() +} + +// isBase60 returns whether s is in base 60 notation as defined in YAML 1.1. +// +// The base 60 float notation in YAML 1.1 is a terrible idea and is unsupported +// in YAML 1.2 and by this package, but these should be marshalled quoted for +// the time being for compatibility with other parsers. +func isBase60Float(s string) (result bool) { + // Fast path. + if s == "" { + return false + } + c := s[0] + if !(c == '+' || c == '-' || c >= '0' && c <= '9') || strings.IndexByte(s, ':') < 0 { + return false + } + // Do the full match. + return base60float.MatchString(s) +} + +// From http://yaml.org/type/float.html, except the regular expression there +// is bogus. In practice parsers do not enforce the "\.[0-9_]*" suffix. +var base60float = regexp.MustCompile(`^[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+(?:\.[0-9_]*)?$`) + +func (e *encoder) stringv(tag string, in reflect.Value) { + var style yaml_scalar_style_t + s := in.String() + rtag, rs := resolve("", s) + if rtag == yaml_BINARY_TAG { + if tag == "" || tag == yaml_STR_TAG { + tag = rtag + s = rs.(string) + } else if tag == yaml_BINARY_TAG { + failf("explicitly tagged !!binary data must be base64-encoded") + } else { + failf("cannot marshal invalid UTF-8 data as %s", shortTag(tag)) + } + } + if tag == "" && (rtag != yaml_STR_TAG || isBase60Float(s)) { + style = yaml_DOUBLE_QUOTED_SCALAR_STYLE + } else if strings.Contains(s, "\n") { + style = yaml_LITERAL_SCALAR_STYLE + } else { + style = yaml_PLAIN_SCALAR_STYLE + } + e.emitScalar(s, "", tag, style) +} + +func (e *encoder) boolv(tag string, in reflect.Value) { + var s string + if in.Bool() { + s = "true" + } else { + s = "false" + } + e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE) +} + +func (e *encoder) intv(tag string, in reflect.Value) { + s := strconv.FormatInt(in.Int(), 10) + e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE) +} + +func (e *encoder) uintv(tag string, in reflect.Value) { + s := strconv.FormatUint(in.Uint(), 10) + e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE) +} + +func (e *encoder) floatv(tag string, in reflect.Value) { + // FIXME: Handle 64 bits here. + s := strconv.FormatFloat(float64(in.Float()), 'g', -1, 32) + switch s { + case "+Inf": + s = ".inf" + case "-Inf": + s = "-.inf" + case "NaN": + s = ".nan" + } + e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE) +} + +func (e *encoder) nilv() { + e.emitScalar("null", "", "", yaml_PLAIN_SCALAR_STYLE) +} + +func (e *encoder) emitScalar(value, anchor, tag string, style yaml_scalar_style_t) { + implicit := tag == "" + e.must(yaml_scalar_event_initialize(&e.event, []byte(anchor), []byte(tag), []byte(value), implicit, implicit, style)) + e.emit() +} diff --git a/vendor/gopkg.in/yaml.v2/parserc.go b/vendor/gopkg.in/yaml.v2/parserc.go new file mode 100644 index 000000000..0a7037ad1 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/parserc.go @@ -0,0 +1,1096 @@ +package yaml + +import ( + "bytes" +) + +// The parser implements the following grammar: +// +// stream ::= STREAM-START implicit_document? explicit_document* STREAM-END +// implicit_document ::= block_node DOCUMENT-END* +// explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* +// block_node_or_indentless_sequence ::= +// ALIAS +// | properties (block_content | indentless_block_sequence)? +// | block_content +// | indentless_block_sequence +// block_node ::= ALIAS +// | properties block_content? +// | block_content +// flow_node ::= ALIAS +// | properties flow_content? +// | flow_content +// properties ::= TAG ANCHOR? | ANCHOR TAG? +// block_content ::= block_collection | flow_collection | SCALAR +// flow_content ::= flow_collection | SCALAR +// block_collection ::= block_sequence | block_mapping +// flow_collection ::= flow_sequence | flow_mapping +// block_sequence ::= BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)* BLOCK-END +// indentless_sequence ::= (BLOCK-ENTRY block_node?)+ +// block_mapping ::= BLOCK-MAPPING_START +// ((KEY block_node_or_indentless_sequence?)? +// (VALUE block_node_or_indentless_sequence?)?)* +// BLOCK-END +// flow_sequence ::= FLOW-SEQUENCE-START +// (flow_sequence_entry FLOW-ENTRY)* +// flow_sequence_entry? +// FLOW-SEQUENCE-END +// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// flow_mapping ::= FLOW-MAPPING-START +// (flow_mapping_entry FLOW-ENTRY)* +// flow_mapping_entry? +// FLOW-MAPPING-END +// flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? + +// Peek the next token in the token queue. +func peek_token(parser *yaml_parser_t) *yaml_token_t { + if parser.token_available || yaml_parser_fetch_more_tokens(parser) { + return &parser.tokens[parser.tokens_head] + } + return nil +} + +// Remove the next token from the queue (must be called after peek_token). +func skip_token(parser *yaml_parser_t) { + parser.token_available = false + parser.tokens_parsed++ + parser.stream_end_produced = parser.tokens[parser.tokens_head].typ == yaml_STREAM_END_TOKEN + parser.tokens_head++ +} + +// Get the next event. +func yaml_parser_parse(parser *yaml_parser_t, event *yaml_event_t) bool { + // Erase the event object. + *event = yaml_event_t{} + + // No events after the end of the stream or error. + if parser.stream_end_produced || parser.error != yaml_NO_ERROR || parser.state == yaml_PARSE_END_STATE { + return true + } + + // Generate the next event. + return yaml_parser_state_machine(parser, event) +} + +// Set parser error. +func yaml_parser_set_parser_error(parser *yaml_parser_t, problem string, problem_mark yaml_mark_t) bool { + parser.error = yaml_PARSER_ERROR + parser.problem = problem + parser.problem_mark = problem_mark + return false +} + +func yaml_parser_set_parser_error_context(parser *yaml_parser_t, context string, context_mark yaml_mark_t, problem string, problem_mark yaml_mark_t) bool { + parser.error = yaml_PARSER_ERROR + parser.context = context + parser.context_mark = context_mark + parser.problem = problem + parser.problem_mark = problem_mark + return false +} + +// State dispatcher. +func yaml_parser_state_machine(parser *yaml_parser_t, event *yaml_event_t) bool { + //trace("yaml_parser_state_machine", "state:", parser.state.String()) + + switch parser.state { + case yaml_PARSE_STREAM_START_STATE: + return yaml_parser_parse_stream_start(parser, event) + + case yaml_PARSE_IMPLICIT_DOCUMENT_START_STATE: + return yaml_parser_parse_document_start(parser, event, true) + + case yaml_PARSE_DOCUMENT_START_STATE: + return yaml_parser_parse_document_start(parser, event, false) + + case yaml_PARSE_DOCUMENT_CONTENT_STATE: + return yaml_parser_parse_document_content(parser, event) + + case yaml_PARSE_DOCUMENT_END_STATE: + return yaml_parser_parse_document_end(parser, event) + + case yaml_PARSE_BLOCK_NODE_STATE: + return yaml_parser_parse_node(parser, event, true, false) + + case yaml_PARSE_BLOCK_NODE_OR_INDENTLESS_SEQUENCE_STATE: + return yaml_parser_parse_node(parser, event, true, true) + + case yaml_PARSE_FLOW_NODE_STATE: + return yaml_parser_parse_node(parser, event, false, false) + + case yaml_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE: + return yaml_parser_parse_block_sequence_entry(parser, event, true) + + case yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE: + return yaml_parser_parse_block_sequence_entry(parser, event, false) + + case yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE: + return yaml_parser_parse_indentless_sequence_entry(parser, event) + + case yaml_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE: + return yaml_parser_parse_block_mapping_key(parser, event, true) + + case yaml_PARSE_BLOCK_MAPPING_KEY_STATE: + return yaml_parser_parse_block_mapping_key(parser, event, false) + + case yaml_PARSE_BLOCK_MAPPING_VALUE_STATE: + return yaml_parser_parse_block_mapping_value(parser, event) + + case yaml_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE: + return yaml_parser_parse_flow_sequence_entry(parser, event, true) + + case yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE: + return yaml_parser_parse_flow_sequence_entry(parser, event, false) + + case yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE: + return yaml_parser_parse_flow_sequence_entry_mapping_key(parser, event) + + case yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE: + return yaml_parser_parse_flow_sequence_entry_mapping_value(parser, event) + + case yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE: + return yaml_parser_parse_flow_sequence_entry_mapping_end(parser, event) + + case yaml_PARSE_FLOW_MAPPING_FIRST_KEY_STATE: + return yaml_parser_parse_flow_mapping_key(parser, event, true) + + case yaml_PARSE_FLOW_MAPPING_KEY_STATE: + return yaml_parser_parse_flow_mapping_key(parser, event, false) + + case yaml_PARSE_FLOW_MAPPING_VALUE_STATE: + return yaml_parser_parse_flow_mapping_value(parser, event, false) + + case yaml_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE: + return yaml_parser_parse_flow_mapping_value(parser, event, true) + + default: + panic("invalid parser state") + } + return false +} + +// Parse the production: +// stream ::= STREAM-START implicit_document? explicit_document* STREAM-END +// ************ +func yaml_parser_parse_stream_start(parser *yaml_parser_t, event *yaml_event_t) bool { + token := peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_STREAM_START_TOKEN { + return yaml_parser_set_parser_error(parser, "did not find expected ", token.start_mark) + } + parser.state = yaml_PARSE_IMPLICIT_DOCUMENT_START_STATE + *event = yaml_event_t{ + typ: yaml_STREAM_START_EVENT, + start_mark: token.start_mark, + end_mark: token.end_mark, + encoding: token.encoding, + } + skip_token(parser) + return true +} + +// Parse the productions: +// implicit_document ::= block_node DOCUMENT-END* +// * +// explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* +// ************************* +func yaml_parser_parse_document_start(parser *yaml_parser_t, event *yaml_event_t, implicit bool) bool { + + token := peek_token(parser) + if token == nil { + return false + } + + // Parse extra document end indicators. + if !implicit { + for token.typ == yaml_DOCUMENT_END_TOKEN { + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + } + } + + if implicit && token.typ != yaml_VERSION_DIRECTIVE_TOKEN && + token.typ != yaml_TAG_DIRECTIVE_TOKEN && + token.typ != yaml_DOCUMENT_START_TOKEN && + token.typ != yaml_STREAM_END_TOKEN { + // Parse an implicit document. + if !yaml_parser_process_directives(parser, nil, nil) { + return false + } + parser.states = append(parser.states, yaml_PARSE_DOCUMENT_END_STATE) + parser.state = yaml_PARSE_BLOCK_NODE_STATE + + *event = yaml_event_t{ + typ: yaml_DOCUMENT_START_EVENT, + start_mark: token.start_mark, + end_mark: token.end_mark, + } + + } else if token.typ != yaml_STREAM_END_TOKEN { + // Parse an explicit document. + var version_directive *yaml_version_directive_t + var tag_directives []yaml_tag_directive_t + start_mark := token.start_mark + if !yaml_parser_process_directives(parser, &version_directive, &tag_directives) { + return false + } + token = peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_DOCUMENT_START_TOKEN { + yaml_parser_set_parser_error(parser, + "did not find expected ", token.start_mark) + return false + } + parser.states = append(parser.states, yaml_PARSE_DOCUMENT_END_STATE) + parser.state = yaml_PARSE_DOCUMENT_CONTENT_STATE + end_mark := token.end_mark + + *event = yaml_event_t{ + typ: yaml_DOCUMENT_START_EVENT, + start_mark: start_mark, + end_mark: end_mark, + version_directive: version_directive, + tag_directives: tag_directives, + implicit: false, + } + skip_token(parser) + + } else { + // Parse the stream end. + parser.state = yaml_PARSE_END_STATE + *event = yaml_event_t{ + typ: yaml_STREAM_END_EVENT, + start_mark: token.start_mark, + end_mark: token.end_mark, + } + skip_token(parser) + } + + return true +} + +// Parse the productions: +// explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* +// *********** +// +func yaml_parser_parse_document_content(parser *yaml_parser_t, event *yaml_event_t) bool { + token := peek_token(parser) + if token == nil { + return false + } + if token.typ == yaml_VERSION_DIRECTIVE_TOKEN || + token.typ == yaml_TAG_DIRECTIVE_TOKEN || + token.typ == yaml_DOCUMENT_START_TOKEN || + token.typ == yaml_DOCUMENT_END_TOKEN || + token.typ == yaml_STREAM_END_TOKEN { + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + return yaml_parser_process_empty_scalar(parser, event, + token.start_mark) + } + return yaml_parser_parse_node(parser, event, true, false) +} + +// Parse the productions: +// implicit_document ::= block_node DOCUMENT-END* +// ************* +// explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* +// +func yaml_parser_parse_document_end(parser *yaml_parser_t, event *yaml_event_t) bool { + token := peek_token(parser) + if token == nil { + return false + } + + start_mark := token.start_mark + end_mark := token.start_mark + + implicit := true + if token.typ == yaml_DOCUMENT_END_TOKEN { + end_mark = token.end_mark + skip_token(parser) + implicit = false + } + + parser.tag_directives = parser.tag_directives[:0] + + parser.state = yaml_PARSE_DOCUMENT_START_STATE + *event = yaml_event_t{ + typ: yaml_DOCUMENT_END_EVENT, + start_mark: start_mark, + end_mark: end_mark, + implicit: implicit, + } + return true +} + +// Parse the productions: +// block_node_or_indentless_sequence ::= +// ALIAS +// ***** +// | properties (block_content | indentless_block_sequence)? +// ********** * +// | block_content | indentless_block_sequence +// * +// block_node ::= ALIAS +// ***** +// | properties block_content? +// ********** * +// | block_content +// * +// flow_node ::= ALIAS +// ***** +// | properties flow_content? +// ********** * +// | flow_content +// * +// properties ::= TAG ANCHOR? | ANCHOR TAG? +// ************************* +// block_content ::= block_collection | flow_collection | SCALAR +// ****** +// flow_content ::= flow_collection | SCALAR +// ****** +func yaml_parser_parse_node(parser *yaml_parser_t, event *yaml_event_t, block, indentless_sequence bool) bool { + //defer trace("yaml_parser_parse_node", "block:", block, "indentless_sequence:", indentless_sequence)() + + token := peek_token(parser) + if token == nil { + return false + } + + if token.typ == yaml_ALIAS_TOKEN { + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + *event = yaml_event_t{ + typ: yaml_ALIAS_EVENT, + start_mark: token.start_mark, + end_mark: token.end_mark, + anchor: token.value, + } + skip_token(parser) + return true + } + + start_mark := token.start_mark + end_mark := token.start_mark + + var tag_token bool + var tag_handle, tag_suffix, anchor []byte + var tag_mark yaml_mark_t + if token.typ == yaml_ANCHOR_TOKEN { + anchor = token.value + start_mark = token.start_mark + end_mark = token.end_mark + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + if token.typ == yaml_TAG_TOKEN { + tag_token = true + tag_handle = token.value + tag_suffix = token.suffix + tag_mark = token.start_mark + end_mark = token.end_mark + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + } + } else if token.typ == yaml_TAG_TOKEN { + tag_token = true + tag_handle = token.value + tag_suffix = token.suffix + start_mark = token.start_mark + tag_mark = token.start_mark + end_mark = token.end_mark + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + if token.typ == yaml_ANCHOR_TOKEN { + anchor = token.value + end_mark = token.end_mark + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + } + } + + var tag []byte + if tag_token { + if len(tag_handle) == 0 { + tag = tag_suffix + tag_suffix = nil + } else { + for i := range parser.tag_directives { + if bytes.Equal(parser.tag_directives[i].handle, tag_handle) { + tag = append([]byte(nil), parser.tag_directives[i].prefix...) + tag = append(tag, tag_suffix...) + break + } + } + if len(tag) == 0 { + yaml_parser_set_parser_error_context(parser, + "while parsing a node", start_mark, + "found undefined tag handle", tag_mark) + return false + } + } + } + + implicit := len(tag) == 0 + if indentless_sequence && token.typ == yaml_BLOCK_ENTRY_TOKEN { + end_mark = token.end_mark + parser.state = yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE + *event = yaml_event_t{ + typ: yaml_SEQUENCE_START_EVENT, + start_mark: start_mark, + end_mark: end_mark, + anchor: anchor, + tag: tag, + implicit: implicit, + style: yaml_style_t(yaml_BLOCK_SEQUENCE_STYLE), + } + return true + } + if token.typ == yaml_SCALAR_TOKEN { + var plain_implicit, quoted_implicit bool + end_mark = token.end_mark + if (len(tag) == 0 && token.style == yaml_PLAIN_SCALAR_STYLE) || (len(tag) == 1 && tag[0] == '!') { + plain_implicit = true + } else if len(tag) == 0 { + quoted_implicit = true + } + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + + *event = yaml_event_t{ + typ: yaml_SCALAR_EVENT, + start_mark: start_mark, + end_mark: end_mark, + anchor: anchor, + tag: tag, + value: token.value, + implicit: plain_implicit, + quoted_implicit: quoted_implicit, + style: yaml_style_t(token.style), + } + skip_token(parser) + return true + } + if token.typ == yaml_FLOW_SEQUENCE_START_TOKEN { + // [Go] Some of the events below can be merged as they differ only on style. + end_mark = token.end_mark + parser.state = yaml_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE + *event = yaml_event_t{ + typ: yaml_SEQUENCE_START_EVENT, + start_mark: start_mark, + end_mark: end_mark, + anchor: anchor, + tag: tag, + implicit: implicit, + style: yaml_style_t(yaml_FLOW_SEQUENCE_STYLE), + } + return true + } + if token.typ == yaml_FLOW_MAPPING_START_TOKEN { + end_mark = token.end_mark + parser.state = yaml_PARSE_FLOW_MAPPING_FIRST_KEY_STATE + *event = yaml_event_t{ + typ: yaml_MAPPING_START_EVENT, + start_mark: start_mark, + end_mark: end_mark, + anchor: anchor, + tag: tag, + implicit: implicit, + style: yaml_style_t(yaml_FLOW_MAPPING_STYLE), + } + return true + } + if block && token.typ == yaml_BLOCK_SEQUENCE_START_TOKEN { + end_mark = token.end_mark + parser.state = yaml_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE + *event = yaml_event_t{ + typ: yaml_SEQUENCE_START_EVENT, + start_mark: start_mark, + end_mark: end_mark, + anchor: anchor, + tag: tag, + implicit: implicit, + style: yaml_style_t(yaml_BLOCK_SEQUENCE_STYLE), + } + return true + } + if block && token.typ == yaml_BLOCK_MAPPING_START_TOKEN { + end_mark = token.end_mark + parser.state = yaml_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE + *event = yaml_event_t{ + typ: yaml_MAPPING_START_EVENT, + start_mark: start_mark, + end_mark: end_mark, + anchor: anchor, + tag: tag, + implicit: implicit, + style: yaml_style_t(yaml_BLOCK_MAPPING_STYLE), + } + return true + } + if len(anchor) > 0 || len(tag) > 0 { + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + + *event = yaml_event_t{ + typ: yaml_SCALAR_EVENT, + start_mark: start_mark, + end_mark: end_mark, + anchor: anchor, + tag: tag, + implicit: implicit, + quoted_implicit: false, + style: yaml_style_t(yaml_PLAIN_SCALAR_STYLE), + } + return true + } + + context := "while parsing a flow node" + if block { + context = "while parsing a block node" + } + yaml_parser_set_parser_error_context(parser, context, start_mark, + "did not find expected node content", token.start_mark) + return false +} + +// Parse the productions: +// block_sequence ::= BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)* BLOCK-END +// ******************** *********** * ********* +// +func yaml_parser_parse_block_sequence_entry(parser *yaml_parser_t, event *yaml_event_t, first bool) bool { + if first { + token := peek_token(parser) + parser.marks = append(parser.marks, token.start_mark) + skip_token(parser) + } + + token := peek_token(parser) + if token == nil { + return false + } + + if token.typ == yaml_BLOCK_ENTRY_TOKEN { + mark := token.end_mark + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_BLOCK_ENTRY_TOKEN && token.typ != yaml_BLOCK_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE) + return yaml_parser_parse_node(parser, event, true, false) + } else { + parser.state = yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE + return yaml_parser_process_empty_scalar(parser, event, mark) + } + } + if token.typ == yaml_BLOCK_END_TOKEN { + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + parser.marks = parser.marks[:len(parser.marks)-1] + + *event = yaml_event_t{ + typ: yaml_SEQUENCE_END_EVENT, + start_mark: token.start_mark, + end_mark: token.end_mark, + } + + skip_token(parser) + return true + } + + context_mark := parser.marks[len(parser.marks)-1] + parser.marks = parser.marks[:len(parser.marks)-1] + return yaml_parser_set_parser_error_context(parser, + "while parsing a block collection", context_mark, + "did not find expected '-' indicator", token.start_mark) +} + +// Parse the productions: +// indentless_sequence ::= (BLOCK-ENTRY block_node?)+ +// *********** * +func yaml_parser_parse_indentless_sequence_entry(parser *yaml_parser_t, event *yaml_event_t) bool { + token := peek_token(parser) + if token == nil { + return false + } + + if token.typ == yaml_BLOCK_ENTRY_TOKEN { + mark := token.end_mark + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_BLOCK_ENTRY_TOKEN && + token.typ != yaml_KEY_TOKEN && + token.typ != yaml_VALUE_TOKEN && + token.typ != yaml_BLOCK_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE) + return yaml_parser_parse_node(parser, event, true, false) + } + parser.state = yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE + return yaml_parser_process_empty_scalar(parser, event, mark) + } + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + + *event = yaml_event_t{ + typ: yaml_SEQUENCE_END_EVENT, + start_mark: token.start_mark, + end_mark: token.start_mark, // [Go] Shouldn't this be token.end_mark? + } + return true +} + +// Parse the productions: +// block_mapping ::= BLOCK-MAPPING_START +// ******************* +// ((KEY block_node_or_indentless_sequence?)? +// *** * +// (VALUE block_node_or_indentless_sequence?)?)* +// +// BLOCK-END +// ********* +// +func yaml_parser_parse_block_mapping_key(parser *yaml_parser_t, event *yaml_event_t, first bool) bool { + if first { + token := peek_token(parser) + parser.marks = append(parser.marks, token.start_mark) + skip_token(parser) + } + + token := peek_token(parser) + if token == nil { + return false + } + + if token.typ == yaml_KEY_TOKEN { + mark := token.end_mark + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_KEY_TOKEN && + token.typ != yaml_VALUE_TOKEN && + token.typ != yaml_BLOCK_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_BLOCK_MAPPING_VALUE_STATE) + return yaml_parser_parse_node(parser, event, true, true) + } else { + parser.state = yaml_PARSE_BLOCK_MAPPING_VALUE_STATE + return yaml_parser_process_empty_scalar(parser, event, mark) + } + } else if token.typ == yaml_BLOCK_END_TOKEN { + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + parser.marks = parser.marks[:len(parser.marks)-1] + *event = yaml_event_t{ + typ: yaml_MAPPING_END_EVENT, + start_mark: token.start_mark, + end_mark: token.end_mark, + } + skip_token(parser) + return true + } + + context_mark := parser.marks[len(parser.marks)-1] + parser.marks = parser.marks[:len(parser.marks)-1] + return yaml_parser_set_parser_error_context(parser, + "while parsing a block mapping", context_mark, + "did not find expected key", token.start_mark) +} + +// Parse the productions: +// block_mapping ::= BLOCK-MAPPING_START +// +// ((KEY block_node_or_indentless_sequence?)? +// +// (VALUE block_node_or_indentless_sequence?)?)* +// ***** * +// BLOCK-END +// +// +func yaml_parser_parse_block_mapping_value(parser *yaml_parser_t, event *yaml_event_t) bool { + token := peek_token(parser) + if token == nil { + return false + } + if token.typ == yaml_VALUE_TOKEN { + mark := token.end_mark + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_KEY_TOKEN && + token.typ != yaml_VALUE_TOKEN && + token.typ != yaml_BLOCK_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_BLOCK_MAPPING_KEY_STATE) + return yaml_parser_parse_node(parser, event, true, true) + } + parser.state = yaml_PARSE_BLOCK_MAPPING_KEY_STATE + return yaml_parser_process_empty_scalar(parser, event, mark) + } + parser.state = yaml_PARSE_BLOCK_MAPPING_KEY_STATE + return yaml_parser_process_empty_scalar(parser, event, token.start_mark) +} + +// Parse the productions: +// flow_sequence ::= FLOW-SEQUENCE-START +// ******************* +// (flow_sequence_entry FLOW-ENTRY)* +// * ********** +// flow_sequence_entry? +// * +// FLOW-SEQUENCE-END +// ***************** +// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// * +// +func yaml_parser_parse_flow_sequence_entry(parser *yaml_parser_t, event *yaml_event_t, first bool) bool { + if first { + token := peek_token(parser) + parser.marks = append(parser.marks, token.start_mark) + skip_token(parser) + } + token := peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_FLOW_SEQUENCE_END_TOKEN { + if !first { + if token.typ == yaml_FLOW_ENTRY_TOKEN { + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + } else { + context_mark := parser.marks[len(parser.marks)-1] + parser.marks = parser.marks[:len(parser.marks)-1] + return yaml_parser_set_parser_error_context(parser, + "while parsing a flow sequence", context_mark, + "did not find expected ',' or ']'", token.start_mark) + } + } + + if token.typ == yaml_KEY_TOKEN { + parser.state = yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE + *event = yaml_event_t{ + typ: yaml_MAPPING_START_EVENT, + start_mark: token.start_mark, + end_mark: token.end_mark, + implicit: true, + style: yaml_style_t(yaml_FLOW_MAPPING_STYLE), + } + skip_token(parser) + return true + } else if token.typ != yaml_FLOW_SEQUENCE_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE) + return yaml_parser_parse_node(parser, event, false, false) + } + } + + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + parser.marks = parser.marks[:len(parser.marks)-1] + + *event = yaml_event_t{ + typ: yaml_SEQUENCE_END_EVENT, + start_mark: token.start_mark, + end_mark: token.end_mark, + } + + skip_token(parser) + return true +} + +// +// Parse the productions: +// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// *** * +// +func yaml_parser_parse_flow_sequence_entry_mapping_key(parser *yaml_parser_t, event *yaml_event_t) bool { + token := peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_VALUE_TOKEN && + token.typ != yaml_FLOW_ENTRY_TOKEN && + token.typ != yaml_FLOW_SEQUENCE_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE) + return yaml_parser_parse_node(parser, event, false, false) + } + mark := token.end_mark + skip_token(parser) + parser.state = yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE + return yaml_parser_process_empty_scalar(parser, event, mark) +} + +// Parse the productions: +// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// ***** * +// +func yaml_parser_parse_flow_sequence_entry_mapping_value(parser *yaml_parser_t, event *yaml_event_t) bool { + token := peek_token(parser) + if token == nil { + return false + } + if token.typ == yaml_VALUE_TOKEN { + skip_token(parser) + token := peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_FLOW_ENTRY_TOKEN && token.typ != yaml_FLOW_SEQUENCE_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE) + return yaml_parser_parse_node(parser, event, false, false) + } + } + parser.state = yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE + return yaml_parser_process_empty_scalar(parser, event, token.start_mark) +} + +// Parse the productions: +// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// * +// +func yaml_parser_parse_flow_sequence_entry_mapping_end(parser *yaml_parser_t, event *yaml_event_t) bool { + token := peek_token(parser) + if token == nil { + return false + } + parser.state = yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE + *event = yaml_event_t{ + typ: yaml_MAPPING_END_EVENT, + start_mark: token.start_mark, + end_mark: token.start_mark, // [Go] Shouldn't this be end_mark? + } + return true +} + +// Parse the productions: +// flow_mapping ::= FLOW-MAPPING-START +// ****************** +// (flow_mapping_entry FLOW-ENTRY)* +// * ********** +// flow_mapping_entry? +// ****************** +// FLOW-MAPPING-END +// **************** +// flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// * *** * +// +func yaml_parser_parse_flow_mapping_key(parser *yaml_parser_t, event *yaml_event_t, first bool) bool { + if first { + token := peek_token(parser) + parser.marks = append(parser.marks, token.start_mark) + skip_token(parser) + } + + token := peek_token(parser) + if token == nil { + return false + } + + if token.typ != yaml_FLOW_MAPPING_END_TOKEN { + if !first { + if token.typ == yaml_FLOW_ENTRY_TOKEN { + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + } else { + context_mark := parser.marks[len(parser.marks)-1] + parser.marks = parser.marks[:len(parser.marks)-1] + return yaml_parser_set_parser_error_context(parser, + "while parsing a flow mapping", context_mark, + "did not find expected ',' or '}'", token.start_mark) + } + } + + if token.typ == yaml_KEY_TOKEN { + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_VALUE_TOKEN && + token.typ != yaml_FLOW_ENTRY_TOKEN && + token.typ != yaml_FLOW_MAPPING_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_FLOW_MAPPING_VALUE_STATE) + return yaml_parser_parse_node(parser, event, false, false) + } else { + parser.state = yaml_PARSE_FLOW_MAPPING_VALUE_STATE + return yaml_parser_process_empty_scalar(parser, event, token.start_mark) + } + } else if token.typ != yaml_FLOW_MAPPING_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE) + return yaml_parser_parse_node(parser, event, false, false) + } + } + + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + parser.marks = parser.marks[:len(parser.marks)-1] + *event = yaml_event_t{ + typ: yaml_MAPPING_END_EVENT, + start_mark: token.start_mark, + end_mark: token.end_mark, + } + skip_token(parser) + return true +} + +// Parse the productions: +// flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// * ***** * +// +func yaml_parser_parse_flow_mapping_value(parser *yaml_parser_t, event *yaml_event_t, empty bool) bool { + token := peek_token(parser) + if token == nil { + return false + } + if empty { + parser.state = yaml_PARSE_FLOW_MAPPING_KEY_STATE + return yaml_parser_process_empty_scalar(parser, event, token.start_mark) + } + if token.typ == yaml_VALUE_TOKEN { + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_FLOW_ENTRY_TOKEN && token.typ != yaml_FLOW_MAPPING_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_FLOW_MAPPING_KEY_STATE) + return yaml_parser_parse_node(parser, event, false, false) + } + } + parser.state = yaml_PARSE_FLOW_MAPPING_KEY_STATE + return yaml_parser_process_empty_scalar(parser, event, token.start_mark) +} + +// Generate an empty scalar event. +func yaml_parser_process_empty_scalar(parser *yaml_parser_t, event *yaml_event_t, mark yaml_mark_t) bool { + *event = yaml_event_t{ + typ: yaml_SCALAR_EVENT, + start_mark: mark, + end_mark: mark, + value: nil, // Empty + implicit: true, + style: yaml_style_t(yaml_PLAIN_SCALAR_STYLE), + } + return true +} + +var default_tag_directives = []yaml_tag_directive_t{ + {[]byte("!"), []byte("!")}, + {[]byte("!!"), []byte("tag:yaml.org,2002:")}, +} + +// Parse directives. +func yaml_parser_process_directives(parser *yaml_parser_t, + version_directive_ref **yaml_version_directive_t, + tag_directives_ref *[]yaml_tag_directive_t) bool { + + var version_directive *yaml_version_directive_t + var tag_directives []yaml_tag_directive_t + + token := peek_token(parser) + if token == nil { + return false + } + + for token.typ == yaml_VERSION_DIRECTIVE_TOKEN || token.typ == yaml_TAG_DIRECTIVE_TOKEN { + if token.typ == yaml_VERSION_DIRECTIVE_TOKEN { + if version_directive != nil { + yaml_parser_set_parser_error(parser, + "found duplicate %YAML directive", token.start_mark) + return false + } + if token.major != 1 || token.minor != 1 { + yaml_parser_set_parser_error(parser, + "found incompatible YAML document", token.start_mark) + return false + } + version_directive = &yaml_version_directive_t{ + major: token.major, + minor: token.minor, + } + } else if token.typ == yaml_TAG_DIRECTIVE_TOKEN { + value := yaml_tag_directive_t{ + handle: token.value, + prefix: token.prefix, + } + if !yaml_parser_append_tag_directive(parser, value, false, token.start_mark) { + return false + } + tag_directives = append(tag_directives, value) + } + + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + } + + for i := range default_tag_directives { + if !yaml_parser_append_tag_directive(parser, default_tag_directives[i], true, token.start_mark) { + return false + } + } + + if version_directive_ref != nil { + *version_directive_ref = version_directive + } + if tag_directives_ref != nil { + *tag_directives_ref = tag_directives + } + return true +} + +// Append a tag directive to the directives stack. +func yaml_parser_append_tag_directive(parser *yaml_parser_t, value yaml_tag_directive_t, allow_duplicates bool, mark yaml_mark_t) bool { + for i := range parser.tag_directives { + if bytes.Equal(value.handle, parser.tag_directives[i].handle) { + if allow_duplicates { + return true + } + return yaml_parser_set_parser_error(parser, "found duplicate %TAG directive", mark) + } + } + + // [Go] I suspect the copy is unnecessary. This was likely done + // because there was no way to track ownership of the data. + value_copy := yaml_tag_directive_t{ + handle: make([]byte, len(value.handle)), + prefix: make([]byte, len(value.prefix)), + } + copy(value_copy.handle, value.handle) + copy(value_copy.prefix, value.prefix) + parser.tag_directives = append(parser.tag_directives, value_copy) + return true +} diff --git a/vendor/gopkg.in/yaml.v2/readerc.go b/vendor/gopkg.in/yaml.v2/readerc.go new file mode 100644 index 000000000..f45079171 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/readerc.go @@ -0,0 +1,394 @@ +package yaml + +import ( + "io" +) + +// Set the reader error and return 0. +func yaml_parser_set_reader_error(parser *yaml_parser_t, problem string, offset int, value int) bool { + parser.error = yaml_READER_ERROR + parser.problem = problem + parser.problem_offset = offset + parser.problem_value = value + return false +} + +// Byte order marks. +const ( + bom_UTF8 = "\xef\xbb\xbf" + bom_UTF16LE = "\xff\xfe" + bom_UTF16BE = "\xfe\xff" +) + +// Determine the input stream encoding by checking the BOM symbol. If no BOM is +// found, the UTF-8 encoding is assumed. Return 1 on success, 0 on failure. +func yaml_parser_determine_encoding(parser *yaml_parser_t) bool { + // Ensure that we had enough bytes in the raw buffer. + for !parser.eof && len(parser.raw_buffer)-parser.raw_buffer_pos < 3 { + if !yaml_parser_update_raw_buffer(parser) { + return false + } + } + + // Determine the encoding. + buf := parser.raw_buffer + pos := parser.raw_buffer_pos + avail := len(buf) - pos + if avail >= 2 && buf[pos] == bom_UTF16LE[0] && buf[pos+1] == bom_UTF16LE[1] { + parser.encoding = yaml_UTF16LE_ENCODING + parser.raw_buffer_pos += 2 + parser.offset += 2 + } else if avail >= 2 && buf[pos] == bom_UTF16BE[0] && buf[pos+1] == bom_UTF16BE[1] { + parser.encoding = yaml_UTF16BE_ENCODING + parser.raw_buffer_pos += 2 + parser.offset += 2 + } else if avail >= 3 && buf[pos] == bom_UTF8[0] && buf[pos+1] == bom_UTF8[1] && buf[pos+2] == bom_UTF8[2] { + parser.encoding = yaml_UTF8_ENCODING + parser.raw_buffer_pos += 3 + parser.offset += 3 + } else { + parser.encoding = yaml_UTF8_ENCODING + } + return true +} + +// Update the raw buffer. +func yaml_parser_update_raw_buffer(parser *yaml_parser_t) bool { + size_read := 0 + + // Return if the raw buffer is full. + if parser.raw_buffer_pos == 0 && len(parser.raw_buffer) == cap(parser.raw_buffer) { + return true + } + + // Return on EOF. + if parser.eof { + return true + } + + // Move the remaining bytes in the raw buffer to the beginning. + if parser.raw_buffer_pos > 0 && parser.raw_buffer_pos < len(parser.raw_buffer) { + copy(parser.raw_buffer, parser.raw_buffer[parser.raw_buffer_pos:]) + } + parser.raw_buffer = parser.raw_buffer[:len(parser.raw_buffer)-parser.raw_buffer_pos] + parser.raw_buffer_pos = 0 + + // Call the read handler to fill the buffer. + size_read, err := parser.read_handler(parser, parser.raw_buffer[len(parser.raw_buffer):cap(parser.raw_buffer)]) + parser.raw_buffer = parser.raw_buffer[:len(parser.raw_buffer)+size_read] + if err == io.EOF { + parser.eof = true + } else if err != nil { + return yaml_parser_set_reader_error(parser, "input error: "+err.Error(), parser.offset, -1) + } + return true +} + +// Ensure that the buffer contains at least `length` characters. +// Return true on success, false on failure. +// +// The length is supposed to be significantly less that the buffer size. +func yaml_parser_update_buffer(parser *yaml_parser_t, length int) bool { + if parser.read_handler == nil { + panic("read handler must be set") + } + + // If the EOF flag is set and the raw buffer is empty, do nothing. + if parser.eof && parser.raw_buffer_pos == len(parser.raw_buffer) { + return true + } + + // Return if the buffer contains enough characters. + if parser.unread >= length { + return true + } + + // Determine the input encoding if it is not known yet. + if parser.encoding == yaml_ANY_ENCODING { + if !yaml_parser_determine_encoding(parser) { + return false + } + } + + // Move the unread characters to the beginning of the buffer. + buffer_len := len(parser.buffer) + if parser.buffer_pos > 0 && parser.buffer_pos < buffer_len { + copy(parser.buffer, parser.buffer[parser.buffer_pos:]) + buffer_len -= parser.buffer_pos + parser.buffer_pos = 0 + } else if parser.buffer_pos == buffer_len { + buffer_len = 0 + parser.buffer_pos = 0 + } + + // Open the whole buffer for writing, and cut it before returning. + parser.buffer = parser.buffer[:cap(parser.buffer)] + + // Fill the buffer until it has enough characters. + first := true + for parser.unread < length { + + // Fill the raw buffer if necessary. + if !first || parser.raw_buffer_pos == len(parser.raw_buffer) { + if !yaml_parser_update_raw_buffer(parser) { + parser.buffer = parser.buffer[:buffer_len] + return false + } + } + first = false + + // Decode the raw buffer. + inner: + for parser.raw_buffer_pos != len(parser.raw_buffer) { + var value rune + var width int + + raw_unread := len(parser.raw_buffer) - parser.raw_buffer_pos + + // Decode the next character. + switch parser.encoding { + case yaml_UTF8_ENCODING: + // Decode a UTF-8 character. Check RFC 3629 + // (http://www.ietf.org/rfc/rfc3629.txt) for more details. + // + // The following table (taken from the RFC) is used for + // decoding. + // + // Char. number range | UTF-8 octet sequence + // (hexadecimal) | (binary) + // --------------------+------------------------------------ + // 0000 0000-0000 007F | 0xxxxxxx + // 0000 0080-0000 07FF | 110xxxxx 10xxxxxx + // 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx + // 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + // + // Additionally, the characters in the range 0xD800-0xDFFF + // are prohibited as they are reserved for use with UTF-16 + // surrogate pairs. + + // Determine the length of the UTF-8 sequence. + octet := parser.raw_buffer[parser.raw_buffer_pos] + switch { + case octet&0x80 == 0x00: + width = 1 + case octet&0xE0 == 0xC0: + width = 2 + case octet&0xF0 == 0xE0: + width = 3 + case octet&0xF8 == 0xF0: + width = 4 + default: + // The leading octet is invalid. + return yaml_parser_set_reader_error(parser, + "invalid leading UTF-8 octet", + parser.offset, int(octet)) + } + + // Check if the raw buffer contains an incomplete character. + if width > raw_unread { + if parser.eof { + return yaml_parser_set_reader_error(parser, + "incomplete UTF-8 octet sequence", + parser.offset, -1) + } + break inner + } + + // Decode the leading octet. + switch { + case octet&0x80 == 0x00: + value = rune(octet & 0x7F) + case octet&0xE0 == 0xC0: + value = rune(octet & 0x1F) + case octet&0xF0 == 0xE0: + value = rune(octet & 0x0F) + case octet&0xF8 == 0xF0: + value = rune(octet & 0x07) + default: + value = 0 + } + + // Check and decode the trailing octets. + for k := 1; k < width; k++ { + octet = parser.raw_buffer[parser.raw_buffer_pos+k] + + // Check if the octet is valid. + if (octet & 0xC0) != 0x80 { + return yaml_parser_set_reader_error(parser, + "invalid trailing UTF-8 octet", + parser.offset+k, int(octet)) + } + + // Decode the octet. + value = (value << 6) + rune(octet&0x3F) + } + + // Check the length of the sequence against the value. + switch { + case width == 1: + case width == 2 && value >= 0x80: + case width == 3 && value >= 0x800: + case width == 4 && value >= 0x10000: + default: + return yaml_parser_set_reader_error(parser, + "invalid length of a UTF-8 sequence", + parser.offset, -1) + } + + // Check the range of the value. + if value >= 0xD800 && value <= 0xDFFF || value > 0x10FFFF { + return yaml_parser_set_reader_error(parser, + "invalid Unicode character", + parser.offset, int(value)) + } + + case yaml_UTF16LE_ENCODING, yaml_UTF16BE_ENCODING: + var low, high int + if parser.encoding == yaml_UTF16LE_ENCODING { + low, high = 0, 1 + } else { + low, high = 1, 0 + } + + // The UTF-16 encoding is not as simple as one might + // naively think. Check RFC 2781 + // (http://www.ietf.org/rfc/rfc2781.txt). + // + // Normally, two subsequent bytes describe a Unicode + // character. However a special technique (called a + // surrogate pair) is used for specifying character + // values larger than 0xFFFF. + // + // A surrogate pair consists of two pseudo-characters: + // high surrogate area (0xD800-0xDBFF) + // low surrogate area (0xDC00-0xDFFF) + // + // The following formulas are used for decoding + // and encoding characters using surrogate pairs: + // + // U = U' + 0x10000 (0x01 00 00 <= U <= 0x10 FF FF) + // U' = yyyyyyyyyyxxxxxxxxxx (0 <= U' <= 0x0F FF FF) + // W1 = 110110yyyyyyyyyy + // W2 = 110111xxxxxxxxxx + // + // where U is the character value, W1 is the high surrogate + // area, W2 is the low surrogate area. + + // Check for incomplete UTF-16 character. + if raw_unread < 2 { + if parser.eof { + return yaml_parser_set_reader_error(parser, + "incomplete UTF-16 character", + parser.offset, -1) + } + break inner + } + + // Get the character. + value = rune(parser.raw_buffer[parser.raw_buffer_pos+low]) + + (rune(parser.raw_buffer[parser.raw_buffer_pos+high]) << 8) + + // Check for unexpected low surrogate area. + if value&0xFC00 == 0xDC00 { + return yaml_parser_set_reader_error(parser, + "unexpected low surrogate area", + parser.offset, int(value)) + } + + // Check for a high surrogate area. + if value&0xFC00 == 0xD800 { + width = 4 + + // Check for incomplete surrogate pair. + if raw_unread < 4 { + if parser.eof { + return yaml_parser_set_reader_error(parser, + "incomplete UTF-16 surrogate pair", + parser.offset, -1) + } + break inner + } + + // Get the next character. + value2 := rune(parser.raw_buffer[parser.raw_buffer_pos+low+2]) + + (rune(parser.raw_buffer[parser.raw_buffer_pos+high+2]) << 8) + + // Check for a low surrogate area. + if value2&0xFC00 != 0xDC00 { + return yaml_parser_set_reader_error(parser, + "expected low surrogate area", + parser.offset+2, int(value2)) + } + + // Generate the value of the surrogate pair. + value = 0x10000 + ((value & 0x3FF) << 10) + (value2 & 0x3FF) + } else { + width = 2 + } + + default: + panic("impossible") + } + + // Check if the character is in the allowed range: + // #x9 | #xA | #xD | [#x20-#x7E] (8 bit) + // | #x85 | [#xA0-#xD7FF] | [#xE000-#xFFFD] (16 bit) + // | [#x10000-#x10FFFF] (32 bit) + switch { + case value == 0x09: + case value == 0x0A: + case value == 0x0D: + case value >= 0x20 && value <= 0x7E: + case value == 0x85: + case value >= 0xA0 && value <= 0xD7FF: + case value >= 0xE000 && value <= 0xFFFD: + case value >= 0x10000 && value <= 0x10FFFF: + default: + return yaml_parser_set_reader_error(parser, + "control characters are not allowed", + parser.offset, int(value)) + } + + // Move the raw pointers. + parser.raw_buffer_pos += width + parser.offset += width + + // Finally put the character into the buffer. + if value <= 0x7F { + // 0000 0000-0000 007F . 0xxxxxxx + parser.buffer[buffer_len+0] = byte(value) + buffer_len += 1 + } else if value <= 0x7FF { + // 0000 0080-0000 07FF . 110xxxxx 10xxxxxx + parser.buffer[buffer_len+0] = byte(0xC0 + (value >> 6)) + parser.buffer[buffer_len+1] = byte(0x80 + (value & 0x3F)) + buffer_len += 2 + } else if value <= 0xFFFF { + // 0000 0800-0000 FFFF . 1110xxxx 10xxxxxx 10xxxxxx + parser.buffer[buffer_len+0] = byte(0xE0 + (value >> 12)) + parser.buffer[buffer_len+1] = byte(0x80 + ((value >> 6) & 0x3F)) + parser.buffer[buffer_len+2] = byte(0x80 + (value & 0x3F)) + buffer_len += 3 + } else { + // 0001 0000-0010 FFFF . 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + parser.buffer[buffer_len+0] = byte(0xF0 + (value >> 18)) + parser.buffer[buffer_len+1] = byte(0x80 + ((value >> 12) & 0x3F)) + parser.buffer[buffer_len+2] = byte(0x80 + ((value >> 6) & 0x3F)) + parser.buffer[buffer_len+3] = byte(0x80 + (value & 0x3F)) + buffer_len += 4 + } + + parser.unread++ + } + + // On EOF, put NUL into the buffer and return. + if parser.eof { + parser.buffer[buffer_len] = 0 + buffer_len++ + parser.unread++ + break + } + } + parser.buffer = parser.buffer[:buffer_len] + return true +} diff --git a/vendor/gopkg.in/yaml.v2/resolve.go b/vendor/gopkg.in/yaml.v2/resolve.go new file mode 100644 index 000000000..93a863274 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/resolve.go @@ -0,0 +1,203 @@ +package yaml + +import ( + "encoding/base64" + "math" + "strconv" + "strings" + "unicode/utf8" +) + +type resolveMapItem struct { + value interface{} + tag string +} + +var resolveTable = make([]byte, 256) +var resolveMap = make(map[string]resolveMapItem) + +func init() { + t := resolveTable + t[int('+')] = 'S' // Sign + t[int('-')] = 'S' + for _, c := range "0123456789" { + t[int(c)] = 'D' // Digit + } + for _, c := range "yYnNtTfFoO~" { + t[int(c)] = 'M' // In map + } + t[int('.')] = '.' // Float (potentially in map) + + var resolveMapList = []struct { + v interface{} + tag string + l []string + }{ + {true, yaml_BOOL_TAG, []string{"y", "Y", "yes", "Yes", "YES"}}, + {true, yaml_BOOL_TAG, []string{"true", "True", "TRUE"}}, + {true, yaml_BOOL_TAG, []string{"on", "On", "ON"}}, + {false, yaml_BOOL_TAG, []string{"n", "N", "no", "No", "NO"}}, + {false, yaml_BOOL_TAG, []string{"false", "False", "FALSE"}}, + {false, yaml_BOOL_TAG, []string{"off", "Off", "OFF"}}, + {nil, yaml_NULL_TAG, []string{"", "~", "null", "Null", "NULL"}}, + {math.NaN(), yaml_FLOAT_TAG, []string{".nan", ".NaN", ".NAN"}}, + {math.Inf(+1), yaml_FLOAT_TAG, []string{".inf", ".Inf", ".INF"}}, + {math.Inf(+1), yaml_FLOAT_TAG, []string{"+.inf", "+.Inf", "+.INF"}}, + {math.Inf(-1), yaml_FLOAT_TAG, []string{"-.inf", "-.Inf", "-.INF"}}, + {"<<", yaml_MERGE_TAG, []string{"<<"}}, + } + + m := resolveMap + for _, item := range resolveMapList { + for _, s := range item.l { + m[s] = resolveMapItem{item.v, item.tag} + } + } +} + +const longTagPrefix = "tag:yaml.org,2002:" + +func shortTag(tag string) string { + // TODO This can easily be made faster and produce less garbage. + if strings.HasPrefix(tag, longTagPrefix) { + return "!!" + tag[len(longTagPrefix):] + } + return tag +} + +func longTag(tag string) string { + if strings.HasPrefix(tag, "!!") { + return longTagPrefix + tag[2:] + } + return tag +} + +func resolvableTag(tag string) bool { + switch tag { + case "", yaml_STR_TAG, yaml_BOOL_TAG, yaml_INT_TAG, yaml_FLOAT_TAG, yaml_NULL_TAG: + return true + } + return false +} + +func resolve(tag string, in string) (rtag string, out interface{}) { + if !resolvableTag(tag) { + return tag, in + } + + defer func() { + switch tag { + case "", rtag, yaml_STR_TAG, yaml_BINARY_TAG: + return + } + failf("cannot decode %s `%s` as a %s", shortTag(rtag), in, shortTag(tag)) + }() + + // Any data is accepted as a !!str or !!binary. + // Otherwise, the prefix is enough of a hint about what it might be. + hint := byte('N') + if in != "" { + hint = resolveTable[in[0]] + } + if hint != 0 && tag != yaml_STR_TAG && tag != yaml_BINARY_TAG { + // Handle things we can lookup in a map. + if item, ok := resolveMap[in]; ok { + return item.tag, item.value + } + + // Base 60 floats are a bad idea, were dropped in YAML 1.2, and + // are purposefully unsupported here. They're still quoted on + // the way out for compatibility with other parser, though. + + switch hint { + case 'M': + // We've already checked the map above. + + case '.': + // Not in the map, so maybe a normal float. + floatv, err := strconv.ParseFloat(in, 64) + if err == nil { + return yaml_FLOAT_TAG, floatv + } + + case 'D', 'S': + // Int, float, or timestamp. + plain := strings.Replace(in, "_", "", -1) + intv, err := strconv.ParseInt(plain, 0, 64) + if err == nil { + if intv == int64(int(intv)) { + return yaml_INT_TAG, int(intv) + } else { + return yaml_INT_TAG, intv + } + } + uintv, err := strconv.ParseUint(plain, 0, 64) + if err == nil { + return yaml_INT_TAG, uintv + } + floatv, err := strconv.ParseFloat(plain, 64) + if err == nil { + return yaml_FLOAT_TAG, floatv + } + if strings.HasPrefix(plain, "0b") { + intv, err := strconv.ParseInt(plain[2:], 2, 64) + if err == nil { + if intv == int64(int(intv)) { + return yaml_INT_TAG, int(intv) + } else { + return yaml_INT_TAG, intv + } + } + uintv, err := strconv.ParseUint(plain[2:], 2, 64) + if err == nil { + return yaml_INT_TAG, uintv + } + } else if strings.HasPrefix(plain, "-0b") { + intv, err := strconv.ParseInt(plain[3:], 2, 64) + if err == nil { + if intv == int64(int(intv)) { + return yaml_INT_TAG, -int(intv) + } else { + return yaml_INT_TAG, -intv + } + } + } + // XXX Handle timestamps here. + + default: + panic("resolveTable item not yet handled: " + string(rune(hint)) + " (with " + in + ")") + } + } + if tag == yaml_BINARY_TAG { + return yaml_BINARY_TAG, in + } + if utf8.ValidString(in) { + return yaml_STR_TAG, in + } + return yaml_BINARY_TAG, encodeBase64(in) +} + +// encodeBase64 encodes s as base64 that is broken up into multiple lines +// as appropriate for the resulting length. +func encodeBase64(s string) string { + const lineLen = 70 + encLen := base64.StdEncoding.EncodedLen(len(s)) + lines := encLen/lineLen + 1 + buf := make([]byte, encLen*2+lines) + in := buf[0:encLen] + out := buf[encLen:] + base64.StdEncoding.Encode(in, []byte(s)) + k := 0 + for i := 0; i < len(in); i += lineLen { + j := i + lineLen + if j > len(in) { + j = len(in) + } + k += copy(out[k:], in[i:j]) + if lines > 1 { + out[k] = '\n' + k++ + } + } + return string(out[:k]) +} diff --git a/vendor/gopkg.in/yaml.v2/scannerc.go b/vendor/gopkg.in/yaml.v2/scannerc.go new file mode 100644 index 000000000..25808000f --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/scannerc.go @@ -0,0 +1,2710 @@ +package yaml + +import ( + "bytes" + "fmt" +) + +// Introduction +// ************ +// +// The following notes assume that you are familiar with the YAML specification +// (http://yaml.org/spec/cvs/current.html). We mostly follow it, although in +// some cases we are less restrictive that it requires. +// +// The process of transforming a YAML stream into a sequence of events is +// divided on two steps: Scanning and Parsing. +// +// The Scanner transforms the input stream into a sequence of tokens, while the +// parser transform the sequence of tokens produced by the Scanner into a +// sequence of parsing events. +// +// The Scanner is rather clever and complicated. The Parser, on the contrary, +// is a straightforward implementation of a recursive-descendant parser (or, +// LL(1) parser, as it is usually called). +// +// Actually there are two issues of Scanning that might be called "clever", the +// rest is quite straightforward. The issues are "block collection start" and +// "simple keys". Both issues are explained below in details. +// +// Here the Scanning step is explained and implemented. We start with the list +// of all the tokens produced by the Scanner together with short descriptions. +// +// Now, tokens: +// +// STREAM-START(encoding) # The stream start. +// STREAM-END # The stream end. +// VERSION-DIRECTIVE(major,minor) # The '%YAML' directive. +// TAG-DIRECTIVE(handle,prefix) # The '%TAG' directive. +// DOCUMENT-START # '---' +// DOCUMENT-END # '...' +// BLOCK-SEQUENCE-START # Indentation increase denoting a block +// BLOCK-MAPPING-START # sequence or a block mapping. +// BLOCK-END # Indentation decrease. +// FLOW-SEQUENCE-START # '[' +// FLOW-SEQUENCE-END # ']' +// BLOCK-SEQUENCE-START # '{' +// BLOCK-SEQUENCE-END # '}' +// BLOCK-ENTRY # '-' +// FLOW-ENTRY # ',' +// KEY # '?' or nothing (simple keys). +// VALUE # ':' +// ALIAS(anchor) # '*anchor' +// ANCHOR(anchor) # '&anchor' +// TAG(handle,suffix) # '!handle!suffix' +// SCALAR(value,style) # A scalar. +// +// The following two tokens are "virtual" tokens denoting the beginning and the +// end of the stream: +// +// STREAM-START(encoding) +// STREAM-END +// +// We pass the information about the input stream encoding with the +// STREAM-START token. +// +// The next two tokens are responsible for tags: +// +// VERSION-DIRECTIVE(major,minor) +// TAG-DIRECTIVE(handle,prefix) +// +// Example: +// +// %YAML 1.1 +// %TAG ! !foo +// %TAG !yaml! tag:yaml.org,2002: +// --- +// +// The correspoding sequence of tokens: +// +// STREAM-START(utf-8) +// VERSION-DIRECTIVE(1,1) +// TAG-DIRECTIVE("!","!foo") +// TAG-DIRECTIVE("!yaml","tag:yaml.org,2002:") +// DOCUMENT-START +// STREAM-END +// +// Note that the VERSION-DIRECTIVE and TAG-DIRECTIVE tokens occupy a whole +// line. +// +// The document start and end indicators are represented by: +// +// DOCUMENT-START +// DOCUMENT-END +// +// Note that if a YAML stream contains an implicit document (without '---' +// and '...' indicators), no DOCUMENT-START and DOCUMENT-END tokens will be +// produced. +// +// In the following examples, we present whole documents together with the +// produced tokens. +// +// 1. An implicit document: +// +// 'a scalar' +// +// Tokens: +// +// STREAM-START(utf-8) +// SCALAR("a scalar",single-quoted) +// STREAM-END +// +// 2. An explicit document: +// +// --- +// 'a scalar' +// ... +// +// Tokens: +// +// STREAM-START(utf-8) +// DOCUMENT-START +// SCALAR("a scalar",single-quoted) +// DOCUMENT-END +// STREAM-END +// +// 3. Several documents in a stream: +// +// 'a scalar' +// --- +// 'another scalar' +// --- +// 'yet another scalar' +// +// Tokens: +// +// STREAM-START(utf-8) +// SCALAR("a scalar",single-quoted) +// DOCUMENT-START +// SCALAR("another scalar",single-quoted) +// DOCUMENT-START +// SCALAR("yet another scalar",single-quoted) +// STREAM-END +// +// We have already introduced the SCALAR token above. The following tokens are +// used to describe aliases, anchors, tag, and scalars: +// +// ALIAS(anchor) +// ANCHOR(anchor) +// TAG(handle,suffix) +// SCALAR(value,style) +// +// The following series of examples illustrate the usage of these tokens: +// +// 1. A recursive sequence: +// +// &A [ *A ] +// +// Tokens: +// +// STREAM-START(utf-8) +// ANCHOR("A") +// FLOW-SEQUENCE-START +// ALIAS("A") +// FLOW-SEQUENCE-END +// STREAM-END +// +// 2. A tagged scalar: +// +// !!float "3.14" # A good approximation. +// +// Tokens: +// +// STREAM-START(utf-8) +// TAG("!!","float") +// SCALAR("3.14",double-quoted) +// STREAM-END +// +// 3. Various scalar styles: +// +// --- # Implicit empty plain scalars do not produce tokens. +// --- a plain scalar +// --- 'a single-quoted scalar' +// --- "a double-quoted scalar" +// --- |- +// a literal scalar +// --- >- +// a folded +// scalar +// +// Tokens: +// +// STREAM-START(utf-8) +// DOCUMENT-START +// DOCUMENT-START +// SCALAR("a plain scalar",plain) +// DOCUMENT-START +// SCALAR("a single-quoted scalar",single-quoted) +// DOCUMENT-START +// SCALAR("a double-quoted scalar",double-quoted) +// DOCUMENT-START +// SCALAR("a literal scalar",literal) +// DOCUMENT-START +// SCALAR("a folded scalar",folded) +// STREAM-END +// +// Now it's time to review collection-related tokens. We will start with +// flow collections: +// +// FLOW-SEQUENCE-START +// FLOW-SEQUENCE-END +// FLOW-MAPPING-START +// FLOW-MAPPING-END +// FLOW-ENTRY +// KEY +// VALUE +// +// The tokens FLOW-SEQUENCE-START, FLOW-SEQUENCE-END, FLOW-MAPPING-START, and +// FLOW-MAPPING-END represent the indicators '[', ']', '{', and '}' +// correspondingly. FLOW-ENTRY represent the ',' indicator. Finally the +// indicators '?' and ':', which are used for denoting mapping keys and values, +// are represented by the KEY and VALUE tokens. +// +// The following examples show flow collections: +// +// 1. A flow sequence: +// +// [item 1, item 2, item 3] +// +// Tokens: +// +// STREAM-START(utf-8) +// FLOW-SEQUENCE-START +// SCALAR("item 1",plain) +// FLOW-ENTRY +// SCALAR("item 2",plain) +// FLOW-ENTRY +// SCALAR("item 3",plain) +// FLOW-SEQUENCE-END +// STREAM-END +// +// 2. A flow mapping: +// +// { +// a simple key: a value, # Note that the KEY token is produced. +// ? a complex key: another value, +// } +// +// Tokens: +// +// STREAM-START(utf-8) +// FLOW-MAPPING-START +// KEY +// SCALAR("a simple key",plain) +// VALUE +// SCALAR("a value",plain) +// FLOW-ENTRY +// KEY +// SCALAR("a complex key",plain) +// VALUE +// SCALAR("another value",plain) +// FLOW-ENTRY +// FLOW-MAPPING-END +// STREAM-END +// +// A simple key is a key which is not denoted by the '?' indicator. Note that +// the Scanner still produce the KEY token whenever it encounters a simple key. +// +// For scanning block collections, the following tokens are used (note that we +// repeat KEY and VALUE here): +// +// BLOCK-SEQUENCE-START +// BLOCK-MAPPING-START +// BLOCK-END +// BLOCK-ENTRY +// KEY +// VALUE +// +// The tokens BLOCK-SEQUENCE-START and BLOCK-MAPPING-START denote indentation +// increase that precedes a block collection (cf. the INDENT token in Python). +// The token BLOCK-END denote indentation decrease that ends a block collection +// (cf. the DEDENT token in Python). However YAML has some syntax pecularities +// that makes detections of these tokens more complex. +// +// The tokens BLOCK-ENTRY, KEY, and VALUE are used to represent the indicators +// '-', '?', and ':' correspondingly. +// +// The following examples show how the tokens BLOCK-SEQUENCE-START, +// BLOCK-MAPPING-START, and BLOCK-END are emitted by the Scanner: +// +// 1. Block sequences: +// +// - item 1 +// - item 2 +// - +// - item 3.1 +// - item 3.2 +// - +// key 1: value 1 +// key 2: value 2 +// +// Tokens: +// +// STREAM-START(utf-8) +// BLOCK-SEQUENCE-START +// BLOCK-ENTRY +// SCALAR("item 1",plain) +// BLOCK-ENTRY +// SCALAR("item 2",plain) +// BLOCK-ENTRY +// BLOCK-SEQUENCE-START +// BLOCK-ENTRY +// SCALAR("item 3.1",plain) +// BLOCK-ENTRY +// SCALAR("item 3.2",plain) +// BLOCK-END +// BLOCK-ENTRY +// BLOCK-MAPPING-START +// KEY +// SCALAR("key 1",plain) +// VALUE +// SCALAR("value 1",plain) +// KEY +// SCALAR("key 2",plain) +// VALUE +// SCALAR("value 2",plain) +// BLOCK-END +// BLOCK-END +// STREAM-END +// +// 2. Block mappings: +// +// a simple key: a value # The KEY token is produced here. +// ? a complex key +// : another value +// a mapping: +// key 1: value 1 +// key 2: value 2 +// a sequence: +// - item 1 +// - item 2 +// +// Tokens: +// +// STREAM-START(utf-8) +// BLOCK-MAPPING-START +// KEY +// SCALAR("a simple key",plain) +// VALUE +// SCALAR("a value",plain) +// KEY +// SCALAR("a complex key",plain) +// VALUE +// SCALAR("another value",plain) +// KEY +// SCALAR("a mapping",plain) +// BLOCK-MAPPING-START +// KEY +// SCALAR("key 1",plain) +// VALUE +// SCALAR("value 1",plain) +// KEY +// SCALAR("key 2",plain) +// VALUE +// SCALAR("value 2",plain) +// BLOCK-END +// KEY +// SCALAR("a sequence",plain) +// VALUE +// BLOCK-SEQUENCE-START +// BLOCK-ENTRY +// SCALAR("item 1",plain) +// BLOCK-ENTRY +// SCALAR("item 2",plain) +// BLOCK-END +// BLOCK-END +// STREAM-END +// +// YAML does not always require to start a new block collection from a new +// line. If the current line contains only '-', '?', and ':' indicators, a new +// block collection may start at the current line. The following examples +// illustrate this case: +// +// 1. Collections in a sequence: +// +// - - item 1 +// - item 2 +// - key 1: value 1 +// key 2: value 2 +// - ? complex key +// : complex value +// +// Tokens: +// +// STREAM-START(utf-8) +// BLOCK-SEQUENCE-START +// BLOCK-ENTRY +// BLOCK-SEQUENCE-START +// BLOCK-ENTRY +// SCALAR("item 1",plain) +// BLOCK-ENTRY +// SCALAR("item 2",plain) +// BLOCK-END +// BLOCK-ENTRY +// BLOCK-MAPPING-START +// KEY +// SCALAR("key 1",plain) +// VALUE +// SCALAR("value 1",plain) +// KEY +// SCALAR("key 2",plain) +// VALUE +// SCALAR("value 2",plain) +// BLOCK-END +// BLOCK-ENTRY +// BLOCK-MAPPING-START +// KEY +// SCALAR("complex key") +// VALUE +// SCALAR("complex value") +// BLOCK-END +// BLOCK-END +// STREAM-END +// +// 2. Collections in a mapping: +// +// ? a sequence +// : - item 1 +// - item 2 +// ? a mapping +// : key 1: value 1 +// key 2: value 2 +// +// Tokens: +// +// STREAM-START(utf-8) +// BLOCK-MAPPING-START +// KEY +// SCALAR("a sequence",plain) +// VALUE +// BLOCK-SEQUENCE-START +// BLOCK-ENTRY +// SCALAR("item 1",plain) +// BLOCK-ENTRY +// SCALAR("item 2",plain) +// BLOCK-END +// KEY +// SCALAR("a mapping",plain) +// VALUE +// BLOCK-MAPPING-START +// KEY +// SCALAR("key 1",plain) +// VALUE +// SCALAR("value 1",plain) +// KEY +// SCALAR("key 2",plain) +// VALUE +// SCALAR("value 2",plain) +// BLOCK-END +// BLOCK-END +// STREAM-END +// +// YAML also permits non-indented sequences if they are included into a block +// mapping. In this case, the token BLOCK-SEQUENCE-START is not produced: +// +// key: +// - item 1 # BLOCK-SEQUENCE-START is NOT produced here. +// - item 2 +// +// Tokens: +// +// STREAM-START(utf-8) +// BLOCK-MAPPING-START +// KEY +// SCALAR("key",plain) +// VALUE +// BLOCK-ENTRY +// SCALAR("item 1",plain) +// BLOCK-ENTRY +// SCALAR("item 2",plain) +// BLOCK-END +// + +// Ensure that the buffer contains the required number of characters. +// Return true on success, false on failure (reader error or memory error). +func cache(parser *yaml_parser_t, length int) bool { + // [Go] This was inlined: !cache(A, B) -> unread < B && !update(A, B) + return parser.unread >= length || yaml_parser_update_buffer(parser, length) +} + +// Advance the buffer pointer. +func skip(parser *yaml_parser_t) { + parser.mark.index++ + parser.mark.column++ + parser.unread-- + parser.buffer_pos += width(parser.buffer[parser.buffer_pos]) +} + +func skip_line(parser *yaml_parser_t) { + if is_crlf(parser.buffer, parser.buffer_pos) { + parser.mark.index += 2 + parser.mark.column = 0 + parser.mark.line++ + parser.unread -= 2 + parser.buffer_pos += 2 + } else if is_break(parser.buffer, parser.buffer_pos) { + parser.mark.index++ + parser.mark.column = 0 + parser.mark.line++ + parser.unread-- + parser.buffer_pos += width(parser.buffer[parser.buffer_pos]) + } +} + +// Copy a character to a string buffer and advance pointers. +func read(parser *yaml_parser_t, s []byte) []byte { + w := width(parser.buffer[parser.buffer_pos]) + if w == 0 { + panic("invalid character sequence") + } + if len(s) == 0 { + s = make([]byte, 0, 32) + } + if w == 1 && len(s)+w <= cap(s) { + s = s[:len(s)+1] + s[len(s)-1] = parser.buffer[parser.buffer_pos] + parser.buffer_pos++ + } else { + s = append(s, parser.buffer[parser.buffer_pos:parser.buffer_pos+w]...) + parser.buffer_pos += w + } + parser.mark.index++ + parser.mark.column++ + parser.unread-- + return s +} + +// Copy a line break character to a string buffer and advance pointers. +func read_line(parser *yaml_parser_t, s []byte) []byte { + buf := parser.buffer + pos := parser.buffer_pos + switch { + case buf[pos] == '\r' && buf[pos+1] == '\n': + // CR LF . LF + s = append(s, '\n') + parser.buffer_pos += 2 + parser.mark.index++ + parser.unread-- + case buf[pos] == '\r' || buf[pos] == '\n': + // CR|LF . LF + s = append(s, '\n') + parser.buffer_pos += 1 + case buf[pos] == '\xC2' && buf[pos+1] == '\x85': + // NEL . LF + s = append(s, '\n') + parser.buffer_pos += 2 + case buf[pos] == '\xE2' && buf[pos+1] == '\x80' && (buf[pos+2] == '\xA8' || buf[pos+2] == '\xA9'): + // LS|PS . LS|PS + s = append(s, buf[parser.buffer_pos:pos+3]...) + parser.buffer_pos += 3 + default: + return s + } + parser.mark.index++ + parser.mark.column = 0 + parser.mark.line++ + parser.unread-- + return s +} + +// Get the next token. +func yaml_parser_scan(parser *yaml_parser_t, token *yaml_token_t) bool { + // Erase the token object. + *token = yaml_token_t{} // [Go] Is this necessary? + + // No tokens after STREAM-END or error. + if parser.stream_end_produced || parser.error != yaml_NO_ERROR { + return true + } + + // Ensure that the tokens queue contains enough tokens. + if !parser.token_available { + if !yaml_parser_fetch_more_tokens(parser) { + return false + } + } + + // Fetch the next token from the queue. + *token = parser.tokens[parser.tokens_head] + parser.tokens_head++ + parser.tokens_parsed++ + parser.token_available = false + + if token.typ == yaml_STREAM_END_TOKEN { + parser.stream_end_produced = true + } + return true +} + +// Set the scanner error and return false. +func yaml_parser_set_scanner_error(parser *yaml_parser_t, context string, context_mark yaml_mark_t, problem string) bool { + parser.error = yaml_SCANNER_ERROR + parser.context = context + parser.context_mark = context_mark + parser.problem = problem + parser.problem_mark = parser.mark + return false +} + +func yaml_parser_set_scanner_tag_error(parser *yaml_parser_t, directive bool, context_mark yaml_mark_t, problem string) bool { + context := "while parsing a tag" + if directive { + context = "while parsing a %TAG directive" + } + return yaml_parser_set_scanner_error(parser, context, context_mark, "did not find URI escaped octet") +} + +func trace(args ...interface{}) func() { + pargs := append([]interface{}{"+++"}, args...) + fmt.Println(pargs...) + pargs = append([]interface{}{"---"}, args...) + return func() { fmt.Println(pargs...) } +} + +// Ensure that the tokens queue contains at least one token which can be +// returned to the Parser. +func yaml_parser_fetch_more_tokens(parser *yaml_parser_t) bool { + // While we need more tokens to fetch, do it. + for { + // Check if we really need to fetch more tokens. + need_more_tokens := false + + if parser.tokens_head == len(parser.tokens) { + // Queue is empty. + need_more_tokens = true + } else { + // Check if any potential simple key may occupy the head position. + if !yaml_parser_stale_simple_keys(parser) { + return false + } + + for i := range parser.simple_keys { + simple_key := &parser.simple_keys[i] + if simple_key.possible && simple_key.token_number == parser.tokens_parsed { + need_more_tokens = true + break + } + } + } + + // We are finished. + if !need_more_tokens { + break + } + // Fetch the next token. + if !yaml_parser_fetch_next_token(parser) { + return false + } + } + + parser.token_available = true + return true +} + +// The dispatcher for token fetchers. +func yaml_parser_fetch_next_token(parser *yaml_parser_t) bool { + // Ensure that the buffer is initialized. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + // Check if we just started scanning. Fetch STREAM-START then. + if !parser.stream_start_produced { + return yaml_parser_fetch_stream_start(parser) + } + + // Eat whitespaces and comments until we reach the next token. + if !yaml_parser_scan_to_next_token(parser) { + return false + } + + // Remove obsolete potential simple keys. + if !yaml_parser_stale_simple_keys(parser) { + return false + } + + // Check the indentation level against the current column. + if !yaml_parser_unroll_indent(parser, parser.mark.column) { + return false + } + + // Ensure that the buffer contains at least 4 characters. 4 is the length + // of the longest indicators ('--- ' and '... '). + if parser.unread < 4 && !yaml_parser_update_buffer(parser, 4) { + return false + } + + // Is it the end of the stream? + if is_z(parser.buffer, parser.buffer_pos) { + return yaml_parser_fetch_stream_end(parser) + } + + // Is it a directive? + if parser.mark.column == 0 && parser.buffer[parser.buffer_pos] == '%' { + return yaml_parser_fetch_directive(parser) + } + + buf := parser.buffer + pos := parser.buffer_pos + + // Is it the document start indicator? + if parser.mark.column == 0 && buf[pos] == '-' && buf[pos+1] == '-' && buf[pos+2] == '-' && is_blankz(buf, pos+3) { + return yaml_parser_fetch_document_indicator(parser, yaml_DOCUMENT_START_TOKEN) + } + + // Is it the document end indicator? + if parser.mark.column == 0 && buf[pos] == '.' && buf[pos+1] == '.' && buf[pos+2] == '.' && is_blankz(buf, pos+3) { + return yaml_parser_fetch_document_indicator(parser, yaml_DOCUMENT_END_TOKEN) + } + + // Is it the flow sequence start indicator? + if buf[pos] == '[' { + return yaml_parser_fetch_flow_collection_start(parser, yaml_FLOW_SEQUENCE_START_TOKEN) + } + + // Is it the flow mapping start indicator? + if parser.buffer[parser.buffer_pos] == '{' { + return yaml_parser_fetch_flow_collection_start(parser, yaml_FLOW_MAPPING_START_TOKEN) + } + + // Is it the flow sequence end indicator? + if parser.buffer[parser.buffer_pos] == ']' { + return yaml_parser_fetch_flow_collection_end(parser, + yaml_FLOW_SEQUENCE_END_TOKEN) + } + + // Is it the flow mapping end indicator? + if parser.buffer[parser.buffer_pos] == '}' { + return yaml_parser_fetch_flow_collection_end(parser, + yaml_FLOW_MAPPING_END_TOKEN) + } + + // Is it the flow entry indicator? + if parser.buffer[parser.buffer_pos] == ',' { + return yaml_parser_fetch_flow_entry(parser) + } + + // Is it the block entry indicator? + if parser.buffer[parser.buffer_pos] == '-' && is_blankz(parser.buffer, parser.buffer_pos+1) { + return yaml_parser_fetch_block_entry(parser) + } + + // Is it the key indicator? + if parser.buffer[parser.buffer_pos] == '?' && (parser.flow_level > 0 || is_blankz(parser.buffer, parser.buffer_pos+1)) { + return yaml_parser_fetch_key(parser) + } + + // Is it the value indicator? + if parser.buffer[parser.buffer_pos] == ':' && (parser.flow_level > 0 || is_blankz(parser.buffer, parser.buffer_pos+1)) { + return yaml_parser_fetch_value(parser) + } + + // Is it an alias? + if parser.buffer[parser.buffer_pos] == '*' { + return yaml_parser_fetch_anchor(parser, yaml_ALIAS_TOKEN) + } + + // Is it an anchor? + if parser.buffer[parser.buffer_pos] == '&' { + return yaml_parser_fetch_anchor(parser, yaml_ANCHOR_TOKEN) + } + + // Is it a tag? + if parser.buffer[parser.buffer_pos] == '!' { + return yaml_parser_fetch_tag(parser) + } + + // Is it a literal scalar? + if parser.buffer[parser.buffer_pos] == '|' && parser.flow_level == 0 { + return yaml_parser_fetch_block_scalar(parser, true) + } + + // Is it a folded scalar? + if parser.buffer[parser.buffer_pos] == '>' && parser.flow_level == 0 { + return yaml_parser_fetch_block_scalar(parser, false) + } + + // Is it a single-quoted scalar? + if parser.buffer[parser.buffer_pos] == '\'' { + return yaml_parser_fetch_flow_scalar(parser, true) + } + + // Is it a double-quoted scalar? + if parser.buffer[parser.buffer_pos] == '"' { + return yaml_parser_fetch_flow_scalar(parser, false) + } + + // Is it a plain scalar? + // + // A plain scalar may start with any non-blank characters except + // + // '-', '?', ':', ',', '[', ']', '{', '}', + // '#', '&', '*', '!', '|', '>', '\'', '\"', + // '%', '@', '`'. + // + // In the block context (and, for the '-' indicator, in the flow context + // too), it may also start with the characters + // + // '-', '?', ':' + // + // if it is followed by a non-space character. + // + // The last rule is more restrictive than the specification requires. + // [Go] Make this logic more reasonable. + //switch parser.buffer[parser.buffer_pos] { + //case '-', '?', ':', ',', '?', '-', ',', ':', ']', '[', '}', '{', '&', '#', '!', '*', '>', '|', '"', '\'', '@', '%', '-', '`': + //} + if !(is_blankz(parser.buffer, parser.buffer_pos) || parser.buffer[parser.buffer_pos] == '-' || + parser.buffer[parser.buffer_pos] == '?' || parser.buffer[parser.buffer_pos] == ':' || + parser.buffer[parser.buffer_pos] == ',' || parser.buffer[parser.buffer_pos] == '[' || + parser.buffer[parser.buffer_pos] == ']' || parser.buffer[parser.buffer_pos] == '{' || + parser.buffer[parser.buffer_pos] == '}' || parser.buffer[parser.buffer_pos] == '#' || + parser.buffer[parser.buffer_pos] == '&' || parser.buffer[parser.buffer_pos] == '*' || + parser.buffer[parser.buffer_pos] == '!' || parser.buffer[parser.buffer_pos] == '|' || + parser.buffer[parser.buffer_pos] == '>' || parser.buffer[parser.buffer_pos] == '\'' || + parser.buffer[parser.buffer_pos] == '"' || parser.buffer[parser.buffer_pos] == '%' || + parser.buffer[parser.buffer_pos] == '@' || parser.buffer[parser.buffer_pos] == '`') || + (parser.buffer[parser.buffer_pos] == '-' && !is_blank(parser.buffer, parser.buffer_pos+1)) || + (parser.flow_level == 0 && + (parser.buffer[parser.buffer_pos] == '?' || parser.buffer[parser.buffer_pos] == ':') && + !is_blankz(parser.buffer, parser.buffer_pos+1)) { + return yaml_parser_fetch_plain_scalar(parser) + } + + // If we don't determine the token type so far, it is an error. + return yaml_parser_set_scanner_error(parser, + "while scanning for the next token", parser.mark, + "found character that cannot start any token") +} + +// Check the list of potential simple keys and remove the positions that +// cannot contain simple keys anymore. +func yaml_parser_stale_simple_keys(parser *yaml_parser_t) bool { + // Check for a potential simple key for each flow level. + for i := range parser.simple_keys { + simple_key := &parser.simple_keys[i] + + // The specification requires that a simple key + // + // - is limited to a single line, + // - is shorter than 1024 characters. + if simple_key.possible && (simple_key.mark.line < parser.mark.line || simple_key.mark.index+1024 < parser.mark.index) { + + // Check if the potential simple key to be removed is required. + if simple_key.required { + return yaml_parser_set_scanner_error(parser, + "while scanning a simple key", simple_key.mark, + "could not find expected ':'") + } + simple_key.possible = false + } + } + return true +} + +// Check if a simple key may start at the current position and add it if +// needed. +func yaml_parser_save_simple_key(parser *yaml_parser_t) bool { + // A simple key is required at the current position if the scanner is in + // the block context and the current column coincides with the indentation + // level. + + required := parser.flow_level == 0 && parser.indent == parser.mark.column + + // A simple key is required only when it is the first token in the current + // line. Therefore it is always allowed. But we add a check anyway. + if required && !parser.simple_key_allowed { + panic("should not happen") + } + + // + // If the current position may start a simple key, save it. + // + if parser.simple_key_allowed { + simple_key := yaml_simple_key_t{ + possible: true, + required: required, + token_number: parser.tokens_parsed + (len(parser.tokens) - parser.tokens_head), + } + simple_key.mark = parser.mark + + if !yaml_parser_remove_simple_key(parser) { + return false + } + parser.simple_keys[len(parser.simple_keys)-1] = simple_key + } + return true +} + +// Remove a potential simple key at the current flow level. +func yaml_parser_remove_simple_key(parser *yaml_parser_t) bool { + i := len(parser.simple_keys) - 1 + if parser.simple_keys[i].possible { + // If the key is required, it is an error. + if parser.simple_keys[i].required { + return yaml_parser_set_scanner_error(parser, + "while scanning a simple key", parser.simple_keys[i].mark, + "could not find expected ':'") + } + } + // Remove the key from the stack. + parser.simple_keys[i].possible = false + return true +} + +// Increase the flow level and resize the simple key list if needed. +func yaml_parser_increase_flow_level(parser *yaml_parser_t) bool { + // Reset the simple key on the next level. + parser.simple_keys = append(parser.simple_keys, yaml_simple_key_t{}) + + // Increase the flow level. + parser.flow_level++ + return true +} + +// Decrease the flow level. +func yaml_parser_decrease_flow_level(parser *yaml_parser_t) bool { + if parser.flow_level > 0 { + parser.flow_level-- + parser.simple_keys = parser.simple_keys[:len(parser.simple_keys)-1] + } + return true +} + +// Push the current indentation level to the stack and set the new level +// the current column is greater than the indentation level. In this case, +// append or insert the specified token into the token queue. +func yaml_parser_roll_indent(parser *yaml_parser_t, column, number int, typ yaml_token_type_t, mark yaml_mark_t) bool { + // In the flow context, do nothing. + if parser.flow_level > 0 { + return true + } + + if parser.indent < column { + // Push the current indentation level to the stack and set the new + // indentation level. + parser.indents = append(parser.indents, parser.indent) + parser.indent = column + + // Create a token and insert it into the queue. + token := yaml_token_t{ + typ: typ, + start_mark: mark, + end_mark: mark, + } + if number > -1 { + number -= parser.tokens_parsed + } + yaml_insert_token(parser, number, &token) + } + return true +} + +// Pop indentation levels from the indents stack until the current level +// becomes less or equal to the column. For each indentation level, append +// the BLOCK-END token. +func yaml_parser_unroll_indent(parser *yaml_parser_t, column int) bool { + // In the flow context, do nothing. + if parser.flow_level > 0 { + return true + } + + // Loop through the indentation levels in the stack. + for parser.indent > column { + // Create a token and append it to the queue. + token := yaml_token_t{ + typ: yaml_BLOCK_END_TOKEN, + start_mark: parser.mark, + end_mark: parser.mark, + } + yaml_insert_token(parser, -1, &token) + + // Pop the indentation level. + parser.indent = parser.indents[len(parser.indents)-1] + parser.indents = parser.indents[:len(parser.indents)-1] + } + return true +} + +// Initialize the scanner and produce the STREAM-START token. +func yaml_parser_fetch_stream_start(parser *yaml_parser_t) bool { + + // Set the initial indentation. + parser.indent = -1 + + // Initialize the simple key stack. + parser.simple_keys = append(parser.simple_keys, yaml_simple_key_t{}) + + // A simple key is allowed at the beginning of the stream. + parser.simple_key_allowed = true + + // We have started. + parser.stream_start_produced = true + + // Create the STREAM-START token and append it to the queue. + token := yaml_token_t{ + typ: yaml_STREAM_START_TOKEN, + start_mark: parser.mark, + end_mark: parser.mark, + encoding: parser.encoding, + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the STREAM-END token and shut down the scanner. +func yaml_parser_fetch_stream_end(parser *yaml_parser_t) bool { + + // Force new line. + if parser.mark.column != 0 { + parser.mark.column = 0 + parser.mark.line++ + } + + // Reset the indentation level. + if !yaml_parser_unroll_indent(parser, -1) { + return false + } + + // Reset simple keys. + if !yaml_parser_remove_simple_key(parser) { + return false + } + + parser.simple_key_allowed = false + + // Create the STREAM-END token and append it to the queue. + token := yaml_token_t{ + typ: yaml_STREAM_END_TOKEN, + start_mark: parser.mark, + end_mark: parser.mark, + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce a VERSION-DIRECTIVE or TAG-DIRECTIVE token. +func yaml_parser_fetch_directive(parser *yaml_parser_t) bool { + // Reset the indentation level. + if !yaml_parser_unroll_indent(parser, -1) { + return false + } + + // Reset simple keys. + if !yaml_parser_remove_simple_key(parser) { + return false + } + + parser.simple_key_allowed = false + + // Create the YAML-DIRECTIVE or TAG-DIRECTIVE token. + token := yaml_token_t{} + if !yaml_parser_scan_directive(parser, &token) { + return false + } + // Append the token to the queue. + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the DOCUMENT-START or DOCUMENT-END token. +func yaml_parser_fetch_document_indicator(parser *yaml_parser_t, typ yaml_token_type_t) bool { + // Reset the indentation level. + if !yaml_parser_unroll_indent(parser, -1) { + return false + } + + // Reset simple keys. + if !yaml_parser_remove_simple_key(parser) { + return false + } + + parser.simple_key_allowed = false + + // Consume the token. + start_mark := parser.mark + + skip(parser) + skip(parser) + skip(parser) + + end_mark := parser.mark + + // Create the DOCUMENT-START or DOCUMENT-END token. + token := yaml_token_t{ + typ: typ, + start_mark: start_mark, + end_mark: end_mark, + } + // Append the token to the queue. + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the FLOW-SEQUENCE-START or FLOW-MAPPING-START token. +func yaml_parser_fetch_flow_collection_start(parser *yaml_parser_t, typ yaml_token_type_t) bool { + // The indicators '[' and '{' may start a simple key. + if !yaml_parser_save_simple_key(parser) { + return false + } + + // Increase the flow level. + if !yaml_parser_increase_flow_level(parser) { + return false + } + + // A simple key may follow the indicators '[' and '{'. + parser.simple_key_allowed = true + + // Consume the token. + start_mark := parser.mark + skip(parser) + end_mark := parser.mark + + // Create the FLOW-SEQUENCE-START of FLOW-MAPPING-START token. + token := yaml_token_t{ + typ: typ, + start_mark: start_mark, + end_mark: end_mark, + } + // Append the token to the queue. + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the FLOW-SEQUENCE-END or FLOW-MAPPING-END token. +func yaml_parser_fetch_flow_collection_end(parser *yaml_parser_t, typ yaml_token_type_t) bool { + // Reset any potential simple key on the current flow level. + if !yaml_parser_remove_simple_key(parser) { + return false + } + + // Decrease the flow level. + if !yaml_parser_decrease_flow_level(parser) { + return false + } + + // No simple keys after the indicators ']' and '}'. + parser.simple_key_allowed = false + + // Consume the token. + + start_mark := parser.mark + skip(parser) + end_mark := parser.mark + + // Create the FLOW-SEQUENCE-END of FLOW-MAPPING-END token. + token := yaml_token_t{ + typ: typ, + start_mark: start_mark, + end_mark: end_mark, + } + // Append the token to the queue. + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the FLOW-ENTRY token. +func yaml_parser_fetch_flow_entry(parser *yaml_parser_t) bool { + // Reset any potential simple keys on the current flow level. + if !yaml_parser_remove_simple_key(parser) { + return false + } + + // Simple keys are allowed after ','. + parser.simple_key_allowed = true + + // Consume the token. + start_mark := parser.mark + skip(parser) + end_mark := parser.mark + + // Create the FLOW-ENTRY token and append it to the queue. + token := yaml_token_t{ + typ: yaml_FLOW_ENTRY_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the BLOCK-ENTRY token. +func yaml_parser_fetch_block_entry(parser *yaml_parser_t) bool { + // Check if the scanner is in the block context. + if parser.flow_level == 0 { + // Check if we are allowed to start a new entry. + if !parser.simple_key_allowed { + return yaml_parser_set_scanner_error(parser, "", parser.mark, + "block sequence entries are not allowed in this context") + } + // Add the BLOCK-SEQUENCE-START token if needed. + if !yaml_parser_roll_indent(parser, parser.mark.column, -1, yaml_BLOCK_SEQUENCE_START_TOKEN, parser.mark) { + return false + } + } else { + // It is an error for the '-' indicator to occur in the flow context, + // but we let the Parser detect and report about it because the Parser + // is able to point to the context. + } + + // Reset any potential simple keys on the current flow level. + if !yaml_parser_remove_simple_key(parser) { + return false + } + + // Simple keys are allowed after '-'. + parser.simple_key_allowed = true + + // Consume the token. + start_mark := parser.mark + skip(parser) + end_mark := parser.mark + + // Create the BLOCK-ENTRY token and append it to the queue. + token := yaml_token_t{ + typ: yaml_BLOCK_ENTRY_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the KEY token. +func yaml_parser_fetch_key(parser *yaml_parser_t) bool { + + // In the block context, additional checks are required. + if parser.flow_level == 0 { + // Check if we are allowed to start a new key (not nessesary simple). + if !parser.simple_key_allowed { + return yaml_parser_set_scanner_error(parser, "", parser.mark, + "mapping keys are not allowed in this context") + } + // Add the BLOCK-MAPPING-START token if needed. + if !yaml_parser_roll_indent(parser, parser.mark.column, -1, yaml_BLOCK_MAPPING_START_TOKEN, parser.mark) { + return false + } + } + + // Reset any potential simple keys on the current flow level. + if !yaml_parser_remove_simple_key(parser) { + return false + } + + // Simple keys are allowed after '?' in the block context. + parser.simple_key_allowed = parser.flow_level == 0 + + // Consume the token. + start_mark := parser.mark + skip(parser) + end_mark := parser.mark + + // Create the KEY token and append it to the queue. + token := yaml_token_t{ + typ: yaml_KEY_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the VALUE token. +func yaml_parser_fetch_value(parser *yaml_parser_t) bool { + + simple_key := &parser.simple_keys[len(parser.simple_keys)-1] + + // Have we found a simple key? + if simple_key.possible { + // Create the KEY token and insert it into the queue. + token := yaml_token_t{ + typ: yaml_KEY_TOKEN, + start_mark: simple_key.mark, + end_mark: simple_key.mark, + } + yaml_insert_token(parser, simple_key.token_number-parser.tokens_parsed, &token) + + // In the block context, we may need to add the BLOCK-MAPPING-START token. + if !yaml_parser_roll_indent(parser, simple_key.mark.column, + simple_key.token_number, + yaml_BLOCK_MAPPING_START_TOKEN, simple_key.mark) { + return false + } + + // Remove the simple key. + simple_key.possible = false + + // A simple key cannot follow another simple key. + parser.simple_key_allowed = false + + } else { + // The ':' indicator follows a complex key. + + // In the block context, extra checks are required. + if parser.flow_level == 0 { + + // Check if we are allowed to start a complex value. + if !parser.simple_key_allowed { + return yaml_parser_set_scanner_error(parser, "", parser.mark, + "mapping values are not allowed in this context") + } + + // Add the BLOCK-MAPPING-START token if needed. + if !yaml_parser_roll_indent(parser, parser.mark.column, -1, yaml_BLOCK_MAPPING_START_TOKEN, parser.mark) { + return false + } + } + + // Simple keys after ':' are allowed in the block context. + parser.simple_key_allowed = parser.flow_level == 0 + } + + // Consume the token. + start_mark := parser.mark + skip(parser) + end_mark := parser.mark + + // Create the VALUE token and append it to the queue. + token := yaml_token_t{ + typ: yaml_VALUE_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the ALIAS or ANCHOR token. +func yaml_parser_fetch_anchor(parser *yaml_parser_t, typ yaml_token_type_t) bool { + // An anchor or an alias could be a simple key. + if !yaml_parser_save_simple_key(parser) { + return false + } + + // A simple key cannot follow an anchor or an alias. + parser.simple_key_allowed = false + + // Create the ALIAS or ANCHOR token and append it to the queue. + var token yaml_token_t + if !yaml_parser_scan_anchor(parser, &token, typ) { + return false + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the TAG token. +func yaml_parser_fetch_tag(parser *yaml_parser_t) bool { + // A tag could be a simple key. + if !yaml_parser_save_simple_key(parser) { + return false + } + + // A simple key cannot follow a tag. + parser.simple_key_allowed = false + + // Create the TAG token and append it to the queue. + var token yaml_token_t + if !yaml_parser_scan_tag(parser, &token) { + return false + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the SCALAR(...,literal) or SCALAR(...,folded) tokens. +func yaml_parser_fetch_block_scalar(parser *yaml_parser_t, literal bool) bool { + // Remove any potential simple keys. + if !yaml_parser_remove_simple_key(parser) { + return false + } + + // A simple key may follow a block scalar. + parser.simple_key_allowed = true + + // Create the SCALAR token and append it to the queue. + var token yaml_token_t + if !yaml_parser_scan_block_scalar(parser, &token, literal) { + return false + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the SCALAR(...,single-quoted) or SCALAR(...,double-quoted) tokens. +func yaml_parser_fetch_flow_scalar(parser *yaml_parser_t, single bool) bool { + // A plain scalar could be a simple key. + if !yaml_parser_save_simple_key(parser) { + return false + } + + // A simple key cannot follow a flow scalar. + parser.simple_key_allowed = false + + // Create the SCALAR token and append it to the queue. + var token yaml_token_t + if !yaml_parser_scan_flow_scalar(parser, &token, single) { + return false + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the SCALAR(...,plain) token. +func yaml_parser_fetch_plain_scalar(parser *yaml_parser_t) bool { + // A plain scalar could be a simple key. + if !yaml_parser_save_simple_key(parser) { + return false + } + + // A simple key cannot follow a flow scalar. + parser.simple_key_allowed = false + + // Create the SCALAR token and append it to the queue. + var token yaml_token_t + if !yaml_parser_scan_plain_scalar(parser, &token) { + return false + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Eat whitespaces and comments until the next token is found. +func yaml_parser_scan_to_next_token(parser *yaml_parser_t) bool { + + // Until the next token is not found. + for { + // Allow the BOM mark to start a line. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + if parser.mark.column == 0 && is_bom(parser.buffer, parser.buffer_pos) { + skip(parser) + } + + // Eat whitespaces. + // Tabs are allowed: + // - in the flow context + // - in the block context, but not at the beginning of the line or + // after '-', '?', or ':' (complex value). + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + for parser.buffer[parser.buffer_pos] == ' ' || ((parser.flow_level > 0 || !parser.simple_key_allowed) && parser.buffer[parser.buffer_pos] == '\t') { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Eat a comment until a line break. + if parser.buffer[parser.buffer_pos] == '#' { + for !is_breakz(parser.buffer, parser.buffer_pos) { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + } + + // If it is a line break, eat it. + if is_break(parser.buffer, parser.buffer_pos) { + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + skip_line(parser) + + // In the block context, a new line may start a simple key. + if parser.flow_level == 0 { + parser.simple_key_allowed = true + } + } else { + break // We have found a token. + } + } + + return true +} + +// Scan a YAML-DIRECTIVE or TAG-DIRECTIVE token. +// +// Scope: +// %YAML 1.1 # a comment \n +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +// %TAG !yaml! tag:yaml.org,2002: \n +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +// +func yaml_parser_scan_directive(parser *yaml_parser_t, token *yaml_token_t) bool { + // Eat '%'. + start_mark := parser.mark + skip(parser) + + // Scan the directive name. + var name []byte + if !yaml_parser_scan_directive_name(parser, start_mark, &name) { + return false + } + + // Is it a YAML directive? + if bytes.Equal(name, []byte("YAML")) { + // Scan the VERSION directive value. + var major, minor int8 + if !yaml_parser_scan_version_directive_value(parser, start_mark, &major, &minor) { + return false + } + end_mark := parser.mark + + // Create a VERSION-DIRECTIVE token. + *token = yaml_token_t{ + typ: yaml_VERSION_DIRECTIVE_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + major: major, + minor: minor, + } + + // Is it a TAG directive? + } else if bytes.Equal(name, []byte("TAG")) { + // Scan the TAG directive value. + var handle, prefix []byte + if !yaml_parser_scan_tag_directive_value(parser, start_mark, &handle, &prefix) { + return false + } + end_mark := parser.mark + + // Create a TAG-DIRECTIVE token. + *token = yaml_token_t{ + typ: yaml_TAG_DIRECTIVE_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + value: handle, + prefix: prefix, + } + + // Unknown directive. + } else { + yaml_parser_set_scanner_error(parser, "while scanning a directive", + start_mark, "found unknown directive name") + return false + } + + // Eat the rest of the line including any comments. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + for is_blank(parser.buffer, parser.buffer_pos) { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + if parser.buffer[parser.buffer_pos] == '#' { + for !is_breakz(parser.buffer, parser.buffer_pos) { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + } + + // Check if we are at the end of the line. + if !is_breakz(parser.buffer, parser.buffer_pos) { + yaml_parser_set_scanner_error(parser, "while scanning a directive", + start_mark, "did not find expected comment or line break") + return false + } + + // Eat a line break. + if is_break(parser.buffer, parser.buffer_pos) { + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + skip_line(parser) + } + + return true +} + +// Scan the directive name. +// +// Scope: +// %YAML 1.1 # a comment \n +// ^^^^ +// %TAG !yaml! tag:yaml.org,2002: \n +// ^^^ +// +func yaml_parser_scan_directive_name(parser *yaml_parser_t, start_mark yaml_mark_t, name *[]byte) bool { + // Consume the directive name. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + var s []byte + for is_alpha(parser.buffer, parser.buffer_pos) { + s = read(parser, s) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Check if the name is empty. + if len(s) == 0 { + yaml_parser_set_scanner_error(parser, "while scanning a directive", + start_mark, "could not find expected directive name") + return false + } + + // Check for an blank character after the name. + if !is_blankz(parser.buffer, parser.buffer_pos) { + yaml_parser_set_scanner_error(parser, "while scanning a directive", + start_mark, "found unexpected non-alphabetical character") + return false + } + *name = s + return true +} + +// Scan the value of VERSION-DIRECTIVE. +// +// Scope: +// %YAML 1.1 # a comment \n +// ^^^^^^ +func yaml_parser_scan_version_directive_value(parser *yaml_parser_t, start_mark yaml_mark_t, major, minor *int8) bool { + // Eat whitespaces. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + for is_blank(parser.buffer, parser.buffer_pos) { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Consume the major version number. + if !yaml_parser_scan_version_directive_number(parser, start_mark, major) { + return false + } + + // Eat '.'. + if parser.buffer[parser.buffer_pos] != '.' { + return yaml_parser_set_scanner_error(parser, "while scanning a %YAML directive", + start_mark, "did not find expected digit or '.' character") + } + + skip(parser) + + // Consume the minor version number. + if !yaml_parser_scan_version_directive_number(parser, start_mark, minor) { + return false + } + return true +} + +const max_number_length = 2 + +// Scan the version number of VERSION-DIRECTIVE. +// +// Scope: +// %YAML 1.1 # a comment \n +// ^ +// %YAML 1.1 # a comment \n +// ^ +func yaml_parser_scan_version_directive_number(parser *yaml_parser_t, start_mark yaml_mark_t, number *int8) bool { + + // Repeat while the next character is digit. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + var value, length int8 + for is_digit(parser.buffer, parser.buffer_pos) { + // Check if the number is too long. + length++ + if length > max_number_length { + return yaml_parser_set_scanner_error(parser, "while scanning a %YAML directive", + start_mark, "found extremely long version number") + } + value = value*10 + int8(as_digit(parser.buffer, parser.buffer_pos)) + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Check if the number was present. + if length == 0 { + return yaml_parser_set_scanner_error(parser, "while scanning a %YAML directive", + start_mark, "did not find expected version number") + } + *number = value + return true +} + +// Scan the value of a TAG-DIRECTIVE token. +// +// Scope: +// %TAG !yaml! tag:yaml.org,2002: \n +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +// +func yaml_parser_scan_tag_directive_value(parser *yaml_parser_t, start_mark yaml_mark_t, handle, prefix *[]byte) bool { + var handle_value, prefix_value []byte + + // Eat whitespaces. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + for is_blank(parser.buffer, parser.buffer_pos) { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Scan a handle. + if !yaml_parser_scan_tag_handle(parser, true, start_mark, &handle_value) { + return false + } + + // Expect a whitespace. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + if !is_blank(parser.buffer, parser.buffer_pos) { + yaml_parser_set_scanner_error(parser, "while scanning a %TAG directive", + start_mark, "did not find expected whitespace") + return false + } + + // Eat whitespaces. + for is_blank(parser.buffer, parser.buffer_pos) { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Scan a prefix. + if !yaml_parser_scan_tag_uri(parser, true, nil, start_mark, &prefix_value) { + return false + } + + // Expect a whitespace or line break. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + if !is_blankz(parser.buffer, parser.buffer_pos) { + yaml_parser_set_scanner_error(parser, "while scanning a %TAG directive", + start_mark, "did not find expected whitespace or line break") + return false + } + + *handle = handle_value + *prefix = prefix_value + return true +} + +func yaml_parser_scan_anchor(parser *yaml_parser_t, token *yaml_token_t, typ yaml_token_type_t) bool { + var s []byte + + // Eat the indicator character. + start_mark := parser.mark + skip(parser) + + // Consume the value. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + for is_alpha(parser.buffer, parser.buffer_pos) { + s = read(parser, s) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + end_mark := parser.mark + + /* + * Check if length of the anchor is greater than 0 and it is followed by + * a whitespace character or one of the indicators: + * + * '?', ':', ',', ']', '}', '%', '@', '`'. + */ + + if len(s) == 0 || + !(is_blankz(parser.buffer, parser.buffer_pos) || parser.buffer[parser.buffer_pos] == '?' || + parser.buffer[parser.buffer_pos] == ':' || parser.buffer[parser.buffer_pos] == ',' || + parser.buffer[parser.buffer_pos] == ']' || parser.buffer[parser.buffer_pos] == '}' || + parser.buffer[parser.buffer_pos] == '%' || parser.buffer[parser.buffer_pos] == '@' || + parser.buffer[parser.buffer_pos] == '`') { + context := "while scanning an alias" + if typ == yaml_ANCHOR_TOKEN { + context = "while scanning an anchor" + } + yaml_parser_set_scanner_error(parser, context, start_mark, + "did not find expected alphabetic or numeric character") + return false + } + + // Create a token. + *token = yaml_token_t{ + typ: typ, + start_mark: start_mark, + end_mark: end_mark, + value: s, + } + + return true +} + +/* + * Scan a TAG token. + */ + +func yaml_parser_scan_tag(parser *yaml_parser_t, token *yaml_token_t) bool { + var handle, suffix []byte + + start_mark := parser.mark + + // Check if the tag is in the canonical form. + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + + if parser.buffer[parser.buffer_pos+1] == '<' { + // Keep the handle as '' + + // Eat '!<' + skip(parser) + skip(parser) + + // Consume the tag value. + if !yaml_parser_scan_tag_uri(parser, false, nil, start_mark, &suffix) { + return false + } + + // Check for '>' and eat it. + if parser.buffer[parser.buffer_pos] != '>' { + yaml_parser_set_scanner_error(parser, "while scanning a tag", + start_mark, "did not find the expected '>'") + return false + } + + skip(parser) + } else { + // The tag has either the '!suffix' or the '!handle!suffix' form. + + // First, try to scan a handle. + if !yaml_parser_scan_tag_handle(parser, false, start_mark, &handle) { + return false + } + + // Check if it is, indeed, handle. + if handle[0] == '!' && len(handle) > 1 && handle[len(handle)-1] == '!' { + // Scan the suffix now. + if !yaml_parser_scan_tag_uri(parser, false, nil, start_mark, &suffix) { + return false + } + } else { + // It wasn't a handle after all. Scan the rest of the tag. + if !yaml_parser_scan_tag_uri(parser, false, handle, start_mark, &suffix) { + return false + } + + // Set the handle to '!'. + handle = []byte{'!'} + + // A special case: the '!' tag. Set the handle to '' and the + // suffix to '!'. + if len(suffix) == 0 { + handle, suffix = suffix, handle + } + } + } + + // Check the character which ends the tag. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + if !is_blankz(parser.buffer, parser.buffer_pos) { + yaml_parser_set_scanner_error(parser, "while scanning a tag", + start_mark, "did not find expected whitespace or line break") + return false + } + + end_mark := parser.mark + + // Create a token. + *token = yaml_token_t{ + typ: yaml_TAG_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + value: handle, + suffix: suffix, + } + return true +} + +// Scan a tag handle. +func yaml_parser_scan_tag_handle(parser *yaml_parser_t, directive bool, start_mark yaml_mark_t, handle *[]byte) bool { + // Check the initial '!' character. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + if parser.buffer[parser.buffer_pos] != '!' { + yaml_parser_set_scanner_tag_error(parser, directive, + start_mark, "did not find expected '!'") + return false + } + + var s []byte + + // Copy the '!' character. + s = read(parser, s) + + // Copy all subsequent alphabetical and numerical characters. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + for is_alpha(parser.buffer, parser.buffer_pos) { + s = read(parser, s) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Check if the trailing character is '!' and copy it. + if parser.buffer[parser.buffer_pos] == '!' { + s = read(parser, s) + } else { + // It's either the '!' tag or not really a tag handle. If it's a %TAG + // directive, it's an error. If it's a tag token, it must be a part of URI. + if directive && !(s[0] == '!' && s[1] == 0) { + yaml_parser_set_scanner_tag_error(parser, directive, + start_mark, "did not find expected '!'") + return false + } + } + + *handle = s + return true +} + +// Scan a tag. +func yaml_parser_scan_tag_uri(parser *yaml_parser_t, directive bool, head []byte, start_mark yaml_mark_t, uri *[]byte) bool { + //size_t length = head ? strlen((char *)head) : 0 + var s []byte + + // Copy the head if needed. + // + // Note that we don't copy the leading '!' character. + if len(head) > 1 { + s = append(s, head[1:]...) + } + + // Scan the tag. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + // The set of characters that may appear in URI is as follows: + // + // '0'-'9', 'A'-'Z', 'a'-'z', '_', '-', ';', '/', '?', ':', '@', '&', + // '=', '+', '$', ',', '.', '!', '~', '*', '\'', '(', ')', '[', ']', + // '%'. + // [Go] Convert this into more reasonable logic. + for is_alpha(parser.buffer, parser.buffer_pos) || parser.buffer[parser.buffer_pos] == ';' || + parser.buffer[parser.buffer_pos] == '/' || parser.buffer[parser.buffer_pos] == '?' || + parser.buffer[parser.buffer_pos] == ':' || parser.buffer[parser.buffer_pos] == '@' || + parser.buffer[parser.buffer_pos] == '&' || parser.buffer[parser.buffer_pos] == '=' || + parser.buffer[parser.buffer_pos] == '+' || parser.buffer[parser.buffer_pos] == '$' || + parser.buffer[parser.buffer_pos] == ',' || parser.buffer[parser.buffer_pos] == '.' || + parser.buffer[parser.buffer_pos] == '!' || parser.buffer[parser.buffer_pos] == '~' || + parser.buffer[parser.buffer_pos] == '*' || parser.buffer[parser.buffer_pos] == '\'' || + parser.buffer[parser.buffer_pos] == '(' || parser.buffer[parser.buffer_pos] == ')' || + parser.buffer[parser.buffer_pos] == '[' || parser.buffer[parser.buffer_pos] == ']' || + parser.buffer[parser.buffer_pos] == '%' { + // Check if it is a URI-escape sequence. + if parser.buffer[parser.buffer_pos] == '%' { + if !yaml_parser_scan_uri_escapes(parser, directive, start_mark, &s) { + return false + } + } else { + s = read(parser, s) + } + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Check if the tag is non-empty. + if len(s) == 0 { + yaml_parser_set_scanner_tag_error(parser, directive, + start_mark, "did not find expected tag URI") + return false + } + *uri = s + return true +} + +// Decode an URI-escape sequence corresponding to a single UTF-8 character. +func yaml_parser_scan_uri_escapes(parser *yaml_parser_t, directive bool, start_mark yaml_mark_t, s *[]byte) bool { + + // Decode the required number of characters. + w := 1024 + for w > 0 { + // Check for a URI-escaped octet. + if parser.unread < 3 && !yaml_parser_update_buffer(parser, 3) { + return false + } + + if !(parser.buffer[parser.buffer_pos] == '%' && + is_hex(parser.buffer, parser.buffer_pos+1) && + is_hex(parser.buffer, parser.buffer_pos+2)) { + return yaml_parser_set_scanner_tag_error(parser, directive, + start_mark, "did not find URI escaped octet") + } + + // Get the octet. + octet := byte((as_hex(parser.buffer, parser.buffer_pos+1) << 4) + as_hex(parser.buffer, parser.buffer_pos+2)) + + // If it is the leading octet, determine the length of the UTF-8 sequence. + if w == 1024 { + w = width(octet) + if w == 0 { + return yaml_parser_set_scanner_tag_error(parser, directive, + start_mark, "found an incorrect leading UTF-8 octet") + } + } else { + // Check if the trailing octet is correct. + if octet&0xC0 != 0x80 { + return yaml_parser_set_scanner_tag_error(parser, directive, + start_mark, "found an incorrect trailing UTF-8 octet") + } + } + + // Copy the octet and move the pointers. + *s = append(*s, octet) + skip(parser) + skip(parser) + skip(parser) + w-- + } + return true +} + +// Scan a block scalar. +func yaml_parser_scan_block_scalar(parser *yaml_parser_t, token *yaml_token_t, literal bool) bool { + // Eat the indicator '|' or '>'. + start_mark := parser.mark + skip(parser) + + // Scan the additional block scalar indicators. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + // Check for a chomping indicator. + var chomping, increment int + if parser.buffer[parser.buffer_pos] == '+' || parser.buffer[parser.buffer_pos] == '-' { + // Set the chomping method and eat the indicator. + if parser.buffer[parser.buffer_pos] == '+' { + chomping = +1 + } else { + chomping = -1 + } + skip(parser) + + // Check for an indentation indicator. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + if is_digit(parser.buffer, parser.buffer_pos) { + // Check that the indentation is greater than 0. + if parser.buffer[parser.buffer_pos] == '0' { + yaml_parser_set_scanner_error(parser, "while scanning a block scalar", + start_mark, "found an indentation indicator equal to 0") + return false + } + + // Get the indentation level and eat the indicator. + increment = as_digit(parser.buffer, parser.buffer_pos) + skip(parser) + } + + } else if is_digit(parser.buffer, parser.buffer_pos) { + // Do the same as above, but in the opposite order. + + if parser.buffer[parser.buffer_pos] == '0' { + yaml_parser_set_scanner_error(parser, "while scanning a block scalar", + start_mark, "found an indentation indicator equal to 0") + return false + } + increment = as_digit(parser.buffer, parser.buffer_pos) + skip(parser) + + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + if parser.buffer[parser.buffer_pos] == '+' || parser.buffer[parser.buffer_pos] == '-' { + if parser.buffer[parser.buffer_pos] == '+' { + chomping = +1 + } else { + chomping = -1 + } + skip(parser) + } + } + + // Eat whitespaces and comments to the end of the line. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + for is_blank(parser.buffer, parser.buffer_pos) { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + if parser.buffer[parser.buffer_pos] == '#' { + for !is_breakz(parser.buffer, parser.buffer_pos) { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + } + + // Check if we are at the end of the line. + if !is_breakz(parser.buffer, parser.buffer_pos) { + yaml_parser_set_scanner_error(parser, "while scanning a block scalar", + start_mark, "did not find expected comment or line break") + return false + } + + // Eat a line break. + if is_break(parser.buffer, parser.buffer_pos) { + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + skip_line(parser) + } + + end_mark := parser.mark + + // Set the indentation level if it was specified. + var indent int + if increment > 0 { + if parser.indent >= 0 { + indent = parser.indent + increment + } else { + indent = increment + } + } + + // Scan the leading line breaks and determine the indentation level if needed. + var s, leading_break, trailing_breaks []byte + if !yaml_parser_scan_block_scalar_breaks(parser, &indent, &trailing_breaks, start_mark, &end_mark) { + return false + } + + // Scan the block scalar content. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + var leading_blank, trailing_blank bool + for parser.mark.column == indent && !is_z(parser.buffer, parser.buffer_pos) { + // We are at the beginning of a non-empty line. + + // Is it a trailing whitespace? + trailing_blank = is_blank(parser.buffer, parser.buffer_pos) + + // Check if we need to fold the leading line break. + if !literal && !leading_blank && !trailing_blank && len(leading_break) > 0 && leading_break[0] == '\n' { + // Do we need to join the lines by space? + if len(trailing_breaks) == 0 { + s = append(s, ' ') + } + } else { + s = append(s, leading_break...) + } + leading_break = leading_break[:0] + + // Append the remaining line breaks. + s = append(s, trailing_breaks...) + trailing_breaks = trailing_breaks[:0] + + // Is it a leading whitespace? + leading_blank = is_blank(parser.buffer, parser.buffer_pos) + + // Consume the current line. + for !is_breakz(parser.buffer, parser.buffer_pos) { + s = read(parser, s) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Consume the line break. + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + + leading_break = read_line(parser, leading_break) + + // Eat the following indentation spaces and line breaks. + if !yaml_parser_scan_block_scalar_breaks(parser, &indent, &trailing_breaks, start_mark, &end_mark) { + return false + } + } + + // Chomp the tail. + if chomping != -1 { + s = append(s, leading_break...) + } + if chomping == 1 { + s = append(s, trailing_breaks...) + } + + // Create a token. + *token = yaml_token_t{ + typ: yaml_SCALAR_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + value: s, + style: yaml_LITERAL_SCALAR_STYLE, + } + if !literal { + token.style = yaml_FOLDED_SCALAR_STYLE + } + return true +} + +// Scan indentation spaces and line breaks for a block scalar. Determine the +// indentation level if needed. +func yaml_parser_scan_block_scalar_breaks(parser *yaml_parser_t, indent *int, breaks *[]byte, start_mark yaml_mark_t, end_mark *yaml_mark_t) bool { + *end_mark = parser.mark + + // Eat the indentation spaces and line breaks. + max_indent := 0 + for { + // Eat the indentation spaces. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + for (*indent == 0 || parser.mark.column < *indent) && is_space(parser.buffer, parser.buffer_pos) { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + if parser.mark.column > max_indent { + max_indent = parser.mark.column + } + + // Check for a tab character messing the indentation. + if (*indent == 0 || parser.mark.column < *indent) && is_tab(parser.buffer, parser.buffer_pos) { + return yaml_parser_set_scanner_error(parser, "while scanning a block scalar", + start_mark, "found a tab character where an indentation space is expected") + } + + // Have we found a non-empty line? + if !is_break(parser.buffer, parser.buffer_pos) { + break + } + + // Consume the line break. + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + // [Go] Should really be returning breaks instead. + *breaks = read_line(parser, *breaks) + *end_mark = parser.mark + } + + // Determine the indentation level if needed. + if *indent == 0 { + *indent = max_indent + if *indent < parser.indent+1 { + *indent = parser.indent + 1 + } + if *indent < 1 { + *indent = 1 + } + } + return true +} + +// Scan a quoted scalar. +func yaml_parser_scan_flow_scalar(parser *yaml_parser_t, token *yaml_token_t, single bool) bool { + // Eat the left quote. + start_mark := parser.mark + skip(parser) + + // Consume the content of the quoted scalar. + var s, leading_break, trailing_breaks, whitespaces []byte + for { + // Check that there are no document indicators at the beginning of the line. + if parser.unread < 4 && !yaml_parser_update_buffer(parser, 4) { + return false + } + + if parser.mark.column == 0 && + ((parser.buffer[parser.buffer_pos+0] == '-' && + parser.buffer[parser.buffer_pos+1] == '-' && + parser.buffer[parser.buffer_pos+2] == '-') || + (parser.buffer[parser.buffer_pos+0] == '.' && + parser.buffer[parser.buffer_pos+1] == '.' && + parser.buffer[parser.buffer_pos+2] == '.')) && + is_blankz(parser.buffer, parser.buffer_pos+3) { + yaml_parser_set_scanner_error(parser, "while scanning a quoted scalar", + start_mark, "found unexpected document indicator") + return false + } + + // Check for EOF. + if is_z(parser.buffer, parser.buffer_pos) { + yaml_parser_set_scanner_error(parser, "while scanning a quoted scalar", + start_mark, "found unexpected end of stream") + return false + } + + // Consume non-blank characters. + leading_blanks := false + for !is_blankz(parser.buffer, parser.buffer_pos) { + if single && parser.buffer[parser.buffer_pos] == '\'' && parser.buffer[parser.buffer_pos+1] == '\'' { + // Is is an escaped single quote. + s = append(s, '\'') + skip(parser) + skip(parser) + + } else if single && parser.buffer[parser.buffer_pos] == '\'' { + // It is a right single quote. + break + } else if !single && parser.buffer[parser.buffer_pos] == '"' { + // It is a right double quote. + break + + } else if !single && parser.buffer[parser.buffer_pos] == '\\' && is_break(parser.buffer, parser.buffer_pos+1) { + // It is an escaped line break. + if parser.unread < 3 && !yaml_parser_update_buffer(parser, 3) { + return false + } + skip(parser) + skip_line(parser) + leading_blanks = true + break + + } else if !single && parser.buffer[parser.buffer_pos] == '\\' { + // It is an escape sequence. + code_length := 0 + + // Check the escape character. + switch parser.buffer[parser.buffer_pos+1] { + case '0': + s = append(s, 0) + case 'a': + s = append(s, '\x07') + case 'b': + s = append(s, '\x08') + case 't', '\t': + s = append(s, '\x09') + case 'n': + s = append(s, '\x0A') + case 'v': + s = append(s, '\x0B') + case 'f': + s = append(s, '\x0C') + case 'r': + s = append(s, '\x0D') + case 'e': + s = append(s, '\x1B') + case ' ': + s = append(s, '\x20') + case '"': + s = append(s, '"') + case '\'': + s = append(s, '\'') + case '\\': + s = append(s, '\\') + case 'N': // NEL (#x85) + s = append(s, '\xC2') + s = append(s, '\x85') + case '_': // #xA0 + s = append(s, '\xC2') + s = append(s, '\xA0') + case 'L': // LS (#x2028) + s = append(s, '\xE2') + s = append(s, '\x80') + s = append(s, '\xA8') + case 'P': // PS (#x2029) + s = append(s, '\xE2') + s = append(s, '\x80') + s = append(s, '\xA9') + case 'x': + code_length = 2 + case 'u': + code_length = 4 + case 'U': + code_length = 8 + default: + yaml_parser_set_scanner_error(parser, "while parsing a quoted scalar", + start_mark, "found unknown escape character") + return false + } + + skip(parser) + skip(parser) + + // Consume an arbitrary escape code. + if code_length > 0 { + var value int + + // Scan the character value. + if parser.unread < code_length && !yaml_parser_update_buffer(parser, code_length) { + return false + } + for k := 0; k < code_length; k++ { + if !is_hex(parser.buffer, parser.buffer_pos+k) { + yaml_parser_set_scanner_error(parser, "while parsing a quoted scalar", + start_mark, "did not find expected hexdecimal number") + return false + } + value = (value << 4) + as_hex(parser.buffer, parser.buffer_pos+k) + } + + // Check the value and write the character. + if (value >= 0xD800 && value <= 0xDFFF) || value > 0x10FFFF { + yaml_parser_set_scanner_error(parser, "while parsing a quoted scalar", + start_mark, "found invalid Unicode character escape code") + return false + } + if value <= 0x7F { + s = append(s, byte(value)) + } else if value <= 0x7FF { + s = append(s, byte(0xC0+(value>>6))) + s = append(s, byte(0x80+(value&0x3F))) + } else if value <= 0xFFFF { + s = append(s, byte(0xE0+(value>>12))) + s = append(s, byte(0x80+((value>>6)&0x3F))) + s = append(s, byte(0x80+(value&0x3F))) + } else { + s = append(s, byte(0xF0+(value>>18))) + s = append(s, byte(0x80+((value>>12)&0x3F))) + s = append(s, byte(0x80+((value>>6)&0x3F))) + s = append(s, byte(0x80+(value&0x3F))) + } + + // Advance the pointer. + for k := 0; k < code_length; k++ { + skip(parser) + } + } + } else { + // It is a non-escaped non-blank character. + s = read(parser, s) + } + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + } + + // Check if we are at the end of the scalar. + if single { + if parser.buffer[parser.buffer_pos] == '\'' { + break + } + } else { + if parser.buffer[parser.buffer_pos] == '"' { + break + } + } + + // Consume blank characters. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + for is_blank(parser.buffer, parser.buffer_pos) || is_break(parser.buffer, parser.buffer_pos) { + if is_blank(parser.buffer, parser.buffer_pos) { + // Consume a space or a tab character. + if !leading_blanks { + whitespaces = read(parser, whitespaces) + } else { + skip(parser) + } + } else { + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + + // Check if it is a first line break. + if !leading_blanks { + whitespaces = whitespaces[:0] + leading_break = read_line(parser, leading_break) + leading_blanks = true + } else { + trailing_breaks = read_line(parser, trailing_breaks) + } + } + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Join the whitespaces or fold line breaks. + if leading_blanks { + // Do we need to fold line breaks? + if len(leading_break) > 0 && leading_break[0] == '\n' { + if len(trailing_breaks) == 0 { + s = append(s, ' ') + } else { + s = append(s, trailing_breaks...) + } + } else { + s = append(s, leading_break...) + s = append(s, trailing_breaks...) + } + trailing_breaks = trailing_breaks[:0] + leading_break = leading_break[:0] + } else { + s = append(s, whitespaces...) + whitespaces = whitespaces[:0] + } + } + + // Eat the right quote. + skip(parser) + end_mark := parser.mark + + // Create a token. + *token = yaml_token_t{ + typ: yaml_SCALAR_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + value: s, + style: yaml_SINGLE_QUOTED_SCALAR_STYLE, + } + if !single { + token.style = yaml_DOUBLE_QUOTED_SCALAR_STYLE + } + return true +} + +// Scan a plain scalar. +func yaml_parser_scan_plain_scalar(parser *yaml_parser_t, token *yaml_token_t) bool { + + var s, leading_break, trailing_breaks, whitespaces []byte + var leading_blanks bool + var indent = parser.indent + 1 + + start_mark := parser.mark + end_mark := parser.mark + + // Consume the content of the plain scalar. + for { + // Check for a document indicator. + if parser.unread < 4 && !yaml_parser_update_buffer(parser, 4) { + return false + } + if parser.mark.column == 0 && + ((parser.buffer[parser.buffer_pos+0] == '-' && + parser.buffer[parser.buffer_pos+1] == '-' && + parser.buffer[parser.buffer_pos+2] == '-') || + (parser.buffer[parser.buffer_pos+0] == '.' && + parser.buffer[parser.buffer_pos+1] == '.' && + parser.buffer[parser.buffer_pos+2] == '.')) && + is_blankz(parser.buffer, parser.buffer_pos+3) { + break + } + + // Check for a comment. + if parser.buffer[parser.buffer_pos] == '#' { + break + } + + // Consume non-blank characters. + for !is_blankz(parser.buffer, parser.buffer_pos) { + + // Check for 'x:x' in the flow context. TODO: Fix the test "spec-08-13". + if parser.flow_level > 0 && + parser.buffer[parser.buffer_pos] == ':' && + !is_blankz(parser.buffer, parser.buffer_pos+1) { + yaml_parser_set_scanner_error(parser, "while scanning a plain scalar", + start_mark, "found unexpected ':'") + return false + } + + // Check for indicators that may end a plain scalar. + if (parser.buffer[parser.buffer_pos] == ':' && is_blankz(parser.buffer, parser.buffer_pos+1)) || + (parser.flow_level > 0 && + (parser.buffer[parser.buffer_pos] == ',' || parser.buffer[parser.buffer_pos] == ':' || + parser.buffer[parser.buffer_pos] == '?' || parser.buffer[parser.buffer_pos] == '[' || + parser.buffer[parser.buffer_pos] == ']' || parser.buffer[parser.buffer_pos] == '{' || + parser.buffer[parser.buffer_pos] == '}')) { + break + } + + // Check if we need to join whitespaces and breaks. + if leading_blanks || len(whitespaces) > 0 { + if leading_blanks { + // Do we need to fold line breaks? + if leading_break[0] == '\n' { + if len(trailing_breaks) == 0 { + s = append(s, ' ') + } else { + s = append(s, trailing_breaks...) + } + } else { + s = append(s, leading_break...) + s = append(s, trailing_breaks...) + } + trailing_breaks = trailing_breaks[:0] + leading_break = leading_break[:0] + leading_blanks = false + } else { + s = append(s, whitespaces...) + whitespaces = whitespaces[:0] + } + } + + // Copy the character. + s = read(parser, s) + + end_mark = parser.mark + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + } + + // Is it the end? + if !(is_blank(parser.buffer, parser.buffer_pos) || is_break(parser.buffer, parser.buffer_pos)) { + break + } + + // Consume blank characters. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + for is_blank(parser.buffer, parser.buffer_pos) || is_break(parser.buffer, parser.buffer_pos) { + if is_blank(parser.buffer, parser.buffer_pos) { + + // Check for tab character that abuse indentation. + if leading_blanks && parser.mark.column < indent && is_tab(parser.buffer, parser.buffer_pos) { + yaml_parser_set_scanner_error(parser, "while scanning a plain scalar", + start_mark, "found a tab character that violate indentation") + return false + } + + // Consume a space or a tab character. + if !leading_blanks { + whitespaces = read(parser, whitespaces) + } else { + skip(parser) + } + } else { + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + + // Check if it is a first line break. + if !leading_blanks { + whitespaces = whitespaces[:0] + leading_break = read_line(parser, leading_break) + leading_blanks = true + } else { + trailing_breaks = read_line(parser, trailing_breaks) + } + } + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Check indentation level. + if parser.flow_level == 0 && parser.mark.column < indent { + break + } + } + + // Create a token. + *token = yaml_token_t{ + typ: yaml_SCALAR_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + value: s, + style: yaml_PLAIN_SCALAR_STYLE, + } + + // Note that we change the 'simple_key_allowed' flag. + if leading_blanks { + parser.simple_key_allowed = true + } + return true +} diff --git a/vendor/gopkg.in/yaml.v2/sorter.go b/vendor/gopkg.in/yaml.v2/sorter.go new file mode 100644 index 000000000..5958822f9 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/sorter.go @@ -0,0 +1,104 @@ +package yaml + +import ( + "reflect" + "unicode" +) + +type keyList []reflect.Value + +func (l keyList) Len() int { return len(l) } +func (l keyList) Swap(i, j int) { l[i], l[j] = l[j], l[i] } +func (l keyList) Less(i, j int) bool { + a := l[i] + b := l[j] + ak := a.Kind() + bk := b.Kind() + for (ak == reflect.Interface || ak == reflect.Ptr) && !a.IsNil() { + a = a.Elem() + ak = a.Kind() + } + for (bk == reflect.Interface || bk == reflect.Ptr) && !b.IsNil() { + b = b.Elem() + bk = b.Kind() + } + af, aok := keyFloat(a) + bf, bok := keyFloat(b) + if aok && bok { + if af != bf { + return af < bf + } + if ak != bk { + return ak < bk + } + return numLess(a, b) + } + if ak != reflect.String || bk != reflect.String { + return ak < bk + } + ar, br := []rune(a.String()), []rune(b.String()) + for i := 0; i < len(ar) && i < len(br); i++ { + if ar[i] == br[i] { + continue + } + al := unicode.IsLetter(ar[i]) + bl := unicode.IsLetter(br[i]) + if al && bl { + return ar[i] < br[i] + } + if al || bl { + return bl + } + var ai, bi int + var an, bn int64 + for ai = i; ai < len(ar) && unicode.IsDigit(ar[ai]); ai++ { + an = an*10 + int64(ar[ai]-'0') + } + for bi = i; bi < len(br) && unicode.IsDigit(br[bi]); bi++ { + bn = bn*10 + int64(br[bi]-'0') + } + if an != bn { + return an < bn + } + if ai != bi { + return ai < bi + } + return ar[i] < br[i] + } + return len(ar) < len(br) +} + +// keyFloat returns a float value for v if it is a number/bool +// and whether it is a number/bool or not. +func keyFloat(v reflect.Value) (f float64, ok bool) { + switch v.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return float64(v.Int()), true + case reflect.Float32, reflect.Float64: + return v.Float(), true + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return float64(v.Uint()), true + case reflect.Bool: + if v.Bool() { + return 1, true + } + return 0, true + } + return 0, false +} + +// numLess returns whether a < b. +// a and b must necessarily have the same kind. +func numLess(a, b reflect.Value) bool { + switch a.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return a.Int() < b.Int() + case reflect.Float32, reflect.Float64: + return a.Float() < b.Float() + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return a.Uint() < b.Uint() + case reflect.Bool: + return !a.Bool() && b.Bool() + } + panic("not a number") +} diff --git a/vendor/gopkg.in/yaml.v2/writerc.go b/vendor/gopkg.in/yaml.v2/writerc.go new file mode 100644 index 000000000..190362f25 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/writerc.go @@ -0,0 +1,89 @@ +package yaml + +// Set the writer error and return false. +func yaml_emitter_set_writer_error(emitter *yaml_emitter_t, problem string) bool { + emitter.error = yaml_WRITER_ERROR + emitter.problem = problem + return false +} + +// Flush the output buffer. +func yaml_emitter_flush(emitter *yaml_emitter_t) bool { + if emitter.write_handler == nil { + panic("write handler not set") + } + + // Check if the buffer is empty. + if emitter.buffer_pos == 0 { + return true + } + + // If the output encoding is UTF-8, we don't need to recode the buffer. + if emitter.encoding == yaml_UTF8_ENCODING { + if err := emitter.write_handler(emitter, emitter.buffer[:emitter.buffer_pos]); err != nil { + return yaml_emitter_set_writer_error(emitter, "write error: "+err.Error()) + } + emitter.buffer_pos = 0 + return true + } + + // Recode the buffer into the raw buffer. + var low, high int + if emitter.encoding == yaml_UTF16LE_ENCODING { + low, high = 0, 1 + } else { + high, low = 1, 0 + } + + pos := 0 + for pos < emitter.buffer_pos { + // See the "reader.c" code for more details on UTF-8 encoding. Note + // that we assume that the buffer contains a valid UTF-8 sequence. + + // Read the next UTF-8 character. + octet := emitter.buffer[pos] + + var w int + var value rune + switch { + case octet&0x80 == 0x00: + w, value = 1, rune(octet&0x7F) + case octet&0xE0 == 0xC0: + w, value = 2, rune(octet&0x1F) + case octet&0xF0 == 0xE0: + w, value = 3, rune(octet&0x0F) + case octet&0xF8 == 0xF0: + w, value = 4, rune(octet&0x07) + } + for k := 1; k < w; k++ { + octet = emitter.buffer[pos+k] + value = (value << 6) + (rune(octet) & 0x3F) + } + pos += w + + // Write the character. + if value < 0x10000 { + var b [2]byte + b[high] = byte(value >> 8) + b[low] = byte(value & 0xFF) + emitter.raw_buffer = append(emitter.raw_buffer, b[0], b[1]) + } else { + // Write the character using a surrogate pair (check "reader.c"). + var b [4]byte + value -= 0x10000 + b[high] = byte(0xD8 + (value >> 18)) + b[low] = byte((value >> 10) & 0xFF) + b[high+2] = byte(0xDC + ((value >> 8) & 0xFF)) + b[low+2] = byte(value & 0xFF) + emitter.raw_buffer = append(emitter.raw_buffer, b[0], b[1], b[2], b[3]) + } + } + + // Write the raw buffer. + if err := emitter.write_handler(emitter, emitter.raw_buffer); err != nil { + return yaml_emitter_set_writer_error(emitter, "write error: "+err.Error()) + } + emitter.buffer_pos = 0 + emitter.raw_buffer = emitter.raw_buffer[:0] + return true +} diff --git a/vendor/gopkg.in/yaml.v2/yaml.go b/vendor/gopkg.in/yaml.v2/yaml.go new file mode 100644 index 000000000..36d6b883a --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/yaml.go @@ -0,0 +1,346 @@ +// Package yaml implements YAML support for the Go language. +// +// Source code and other details for the project are available at GitHub: +// +// https://github.com/go-yaml/yaml +// +package yaml + +import ( + "errors" + "fmt" + "reflect" + "strings" + "sync" +) + +// MapSlice encodes and decodes as a YAML map. +// The order of keys is preserved when encoding and decoding. +type MapSlice []MapItem + +// MapItem is an item in a MapSlice. +type MapItem struct { + Key, Value interface{} +} + +// The Unmarshaler interface may be implemented by types to customize their +// behavior when being unmarshaled from a YAML document. The UnmarshalYAML +// method receives a function that may be called to unmarshal the original +// YAML value into a field or variable. It is safe to call the unmarshal +// function parameter more than once if necessary. +type Unmarshaler interface { + UnmarshalYAML(unmarshal func(interface{}) error) error +} + +// The Marshaler interface may be implemented by types to customize their +// behavior when being marshaled into a YAML document. The returned value +// is marshaled in place of the original value implementing Marshaler. +// +// If an error is returned by MarshalYAML, the marshaling procedure stops +// and returns with the provided error. +type Marshaler interface { + MarshalYAML() (interface{}, error) +} + +// Unmarshal decodes the first document found within the in byte slice +// and assigns decoded values into the out value. +// +// Maps and pointers (to a struct, string, int, etc) are accepted as out +// values. If an internal pointer within a struct is not initialized, +// the yaml package will initialize it if necessary for unmarshalling +// the provided data. The out parameter must not be nil. +// +// The type of the decoded values should be compatible with the respective +// values in out. If one or more values cannot be decoded due to a type +// mismatches, decoding continues partially until the end of the YAML +// content, and a *yaml.TypeError is returned with details for all +// missed values. +// +// Struct fields are only unmarshalled if they are exported (have an +// upper case first letter), and are unmarshalled using the field name +// lowercased as the default key. Custom keys may be defined via the +// "yaml" name in the field tag: the content preceding the first comma +// is used as the key, and the following comma-separated options are +// used to tweak the marshalling process (see Marshal). +// Conflicting names result in a runtime error. +// +// For example: +// +// type T struct { +// F int `yaml:"a,omitempty"` +// B int +// } +// var t T +// yaml.Unmarshal([]byte("a: 1\nb: 2"), &t) +// +// See the documentation of Marshal for the format of tags and a list of +// supported tag options. +// +func Unmarshal(in []byte, out interface{}) (err error) { + defer handleErr(&err) + d := newDecoder() + p := newParser(in) + defer p.destroy() + node := p.parse() + if node != nil { + v := reflect.ValueOf(out) + if v.Kind() == reflect.Ptr && !v.IsNil() { + v = v.Elem() + } + d.unmarshal(node, v) + } + if len(d.terrors) > 0 { + return &TypeError{d.terrors} + } + return nil +} + +// Marshal serializes the value provided into a YAML document. The structure +// of the generated document will reflect the structure of the value itself. +// Maps and pointers (to struct, string, int, etc) are accepted as the in value. +// +// Struct fields are only unmarshalled if they are exported (have an upper case +// first letter), and are unmarshalled using the field name lowercased as the +// default key. Custom keys may be defined via the "yaml" name in the field +// tag: the content preceding the first comma is used as the key, and the +// following comma-separated options are used to tweak the marshalling process. +// Conflicting names result in a runtime error. +// +// The field tag format accepted is: +// +// `(...) yaml:"[][,[,]]" (...)` +// +// The following flags are currently supported: +// +// omitempty Only include the field if it's not set to the zero +// value for the type or to empty slices or maps. +// Does not apply to zero valued structs. +// +// flow Marshal using a flow style (useful for structs, +// sequences and maps). +// +// inline Inline the field, which must be a struct or a map, +// causing all of its fields or keys to be processed as if +// they were part of the outer struct. For maps, keys must +// not conflict with the yaml keys of other struct fields. +// +// In addition, if the key is "-", the field is ignored. +// +// For example: +// +// type T struct { +// F int "a,omitempty" +// B int +// } +// yaml.Marshal(&T{B: 2}) // Returns "b: 2\n" +// yaml.Marshal(&T{F: 1}} // Returns "a: 1\nb: 0\n" +// +func Marshal(in interface{}) (out []byte, err error) { + defer handleErr(&err) + e := newEncoder() + defer e.destroy() + e.marshal("", reflect.ValueOf(in)) + e.finish() + out = e.out + return +} + +func handleErr(err *error) { + if v := recover(); v != nil { + if e, ok := v.(yamlError); ok { + *err = e.err + } else { + panic(v) + } + } +} + +type yamlError struct { + err error +} + +func fail(err error) { + panic(yamlError{err}) +} + +func failf(format string, args ...interface{}) { + panic(yamlError{fmt.Errorf("yaml: "+format, args...)}) +} + +// A TypeError is returned by Unmarshal when one or more fields in +// the YAML document cannot be properly decoded into the requested +// types. When this error is returned, the value is still +// unmarshaled partially. +type TypeError struct { + Errors []string +} + +func (e *TypeError) Error() string { + return fmt.Sprintf("yaml: unmarshal errors:\n %s", strings.Join(e.Errors, "\n ")) +} + +// -------------------------------------------------------------------------- +// Maintain a mapping of keys to structure field indexes + +// The code in this section was copied from mgo/bson. + +// structInfo holds details for the serialization of fields of +// a given struct. +type structInfo struct { + FieldsMap map[string]fieldInfo + FieldsList []fieldInfo + + // InlineMap is the number of the field in the struct that + // contains an ,inline map, or -1 if there's none. + InlineMap int +} + +type fieldInfo struct { + Key string + Num int + OmitEmpty bool + Flow bool + + // Inline holds the field index if the field is part of an inlined struct. + Inline []int +} + +var structMap = make(map[reflect.Type]*structInfo) +var fieldMapMutex sync.RWMutex + +func getStructInfo(st reflect.Type) (*structInfo, error) { + fieldMapMutex.RLock() + sinfo, found := structMap[st] + fieldMapMutex.RUnlock() + if found { + return sinfo, nil + } + + n := st.NumField() + fieldsMap := make(map[string]fieldInfo) + fieldsList := make([]fieldInfo, 0, n) + inlineMap := -1 + for i := 0; i != n; i++ { + field := st.Field(i) + if field.PkgPath != "" && !field.Anonymous { + continue // Private field + } + + info := fieldInfo{Num: i} + + tag := field.Tag.Get("yaml") + if tag == "" && strings.Index(string(field.Tag), ":") < 0 { + tag = string(field.Tag) + } + if tag == "-" { + continue + } + + inline := false + fields := strings.Split(tag, ",") + if len(fields) > 1 { + for _, flag := range fields[1:] { + switch flag { + case "omitempty": + info.OmitEmpty = true + case "flow": + info.Flow = true + case "inline": + inline = true + default: + return nil, errors.New(fmt.Sprintf("Unsupported flag %q in tag %q of type %s", flag, tag, st)) + } + } + tag = fields[0] + } + + if inline { + switch field.Type.Kind() { + case reflect.Map: + if inlineMap >= 0 { + return nil, errors.New("Multiple ,inline maps in struct " + st.String()) + } + if field.Type.Key() != reflect.TypeOf("") { + return nil, errors.New("Option ,inline needs a map with string keys in struct " + st.String()) + } + inlineMap = info.Num + case reflect.Struct: + sinfo, err := getStructInfo(field.Type) + if err != nil { + return nil, err + } + for _, finfo := range sinfo.FieldsList { + if _, found := fieldsMap[finfo.Key]; found { + msg := "Duplicated key '" + finfo.Key + "' in struct " + st.String() + return nil, errors.New(msg) + } + if finfo.Inline == nil { + finfo.Inline = []int{i, finfo.Num} + } else { + finfo.Inline = append([]int{i}, finfo.Inline...) + } + fieldsMap[finfo.Key] = finfo + fieldsList = append(fieldsList, finfo) + } + default: + //return nil, errors.New("Option ,inline needs a struct value or map field") + return nil, errors.New("Option ,inline needs a struct value field") + } + continue + } + + if tag != "" { + info.Key = tag + } else { + info.Key = strings.ToLower(field.Name) + } + + if _, found = fieldsMap[info.Key]; found { + msg := "Duplicated key '" + info.Key + "' in struct " + st.String() + return nil, errors.New(msg) + } + + fieldsList = append(fieldsList, info) + fieldsMap[info.Key] = info + } + + sinfo = &structInfo{fieldsMap, fieldsList, inlineMap} + + fieldMapMutex.Lock() + structMap[st] = sinfo + fieldMapMutex.Unlock() + return sinfo, nil +} + +func isZero(v reflect.Value) bool { + switch v.Kind() { + case reflect.String: + return len(v.String()) == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + case reflect.Slice: + return v.Len() == 0 + case reflect.Map: + return v.Len() == 0 + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Struct: + vt := v.Type() + for i := v.NumField() - 1; i >= 0; i-- { + if vt.Field(i).PkgPath != "" { + continue // Private field + } + if !isZero(v.Field(i)) { + return false + } + } + return true + } + return false +} diff --git a/vendor/gopkg.in/yaml.v2/yamlh.go b/vendor/gopkg.in/yaml.v2/yamlh.go new file mode 100644 index 000000000..d60a6b6b0 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/yamlh.go @@ -0,0 +1,716 @@ +package yaml + +import ( + "io" +) + +// The version directive data. +type yaml_version_directive_t struct { + major int8 // The major version number. + minor int8 // The minor version number. +} + +// The tag directive data. +type yaml_tag_directive_t struct { + handle []byte // The tag handle. + prefix []byte // The tag prefix. +} + +type yaml_encoding_t int + +// The stream encoding. +const ( + // Let the parser choose the encoding. + yaml_ANY_ENCODING yaml_encoding_t = iota + + yaml_UTF8_ENCODING // The default UTF-8 encoding. + yaml_UTF16LE_ENCODING // The UTF-16-LE encoding with BOM. + yaml_UTF16BE_ENCODING // The UTF-16-BE encoding with BOM. +) + +type yaml_break_t int + +// Line break types. +const ( + // Let the parser choose the break type. + yaml_ANY_BREAK yaml_break_t = iota + + yaml_CR_BREAK // Use CR for line breaks (Mac style). + yaml_LN_BREAK // Use LN for line breaks (Unix style). + yaml_CRLN_BREAK // Use CR LN for line breaks (DOS style). +) + +type yaml_error_type_t int + +// Many bad things could happen with the parser and emitter. +const ( + // No error is produced. + yaml_NO_ERROR yaml_error_type_t = iota + + yaml_MEMORY_ERROR // Cannot allocate or reallocate a block of memory. + yaml_READER_ERROR // Cannot read or decode the input stream. + yaml_SCANNER_ERROR // Cannot scan the input stream. + yaml_PARSER_ERROR // Cannot parse the input stream. + yaml_COMPOSER_ERROR // Cannot compose a YAML document. + yaml_WRITER_ERROR // Cannot write to the output stream. + yaml_EMITTER_ERROR // Cannot emit a YAML stream. +) + +// The pointer position. +type yaml_mark_t struct { + index int // The position index. + line int // The position line. + column int // The position column. +} + +// Node Styles + +type yaml_style_t int8 + +type yaml_scalar_style_t yaml_style_t + +// Scalar styles. +const ( + // Let the emitter choose the style. + yaml_ANY_SCALAR_STYLE yaml_scalar_style_t = iota + + yaml_PLAIN_SCALAR_STYLE // The plain scalar style. + yaml_SINGLE_QUOTED_SCALAR_STYLE // The single-quoted scalar style. + yaml_DOUBLE_QUOTED_SCALAR_STYLE // The double-quoted scalar style. + yaml_LITERAL_SCALAR_STYLE // The literal scalar style. + yaml_FOLDED_SCALAR_STYLE // The folded scalar style. +) + +type yaml_sequence_style_t yaml_style_t + +// Sequence styles. +const ( + // Let the emitter choose the style. + yaml_ANY_SEQUENCE_STYLE yaml_sequence_style_t = iota + + yaml_BLOCK_SEQUENCE_STYLE // The block sequence style. + yaml_FLOW_SEQUENCE_STYLE // The flow sequence style. +) + +type yaml_mapping_style_t yaml_style_t + +// Mapping styles. +const ( + // Let the emitter choose the style. + yaml_ANY_MAPPING_STYLE yaml_mapping_style_t = iota + + yaml_BLOCK_MAPPING_STYLE // The block mapping style. + yaml_FLOW_MAPPING_STYLE // The flow mapping style. +) + +// Tokens + +type yaml_token_type_t int + +// Token types. +const ( + // An empty token. + yaml_NO_TOKEN yaml_token_type_t = iota + + yaml_STREAM_START_TOKEN // A STREAM-START token. + yaml_STREAM_END_TOKEN // A STREAM-END token. + + yaml_VERSION_DIRECTIVE_TOKEN // A VERSION-DIRECTIVE token. + yaml_TAG_DIRECTIVE_TOKEN // A TAG-DIRECTIVE token. + yaml_DOCUMENT_START_TOKEN // A DOCUMENT-START token. + yaml_DOCUMENT_END_TOKEN // A DOCUMENT-END token. + + yaml_BLOCK_SEQUENCE_START_TOKEN // A BLOCK-SEQUENCE-START token. + yaml_BLOCK_MAPPING_START_TOKEN // A BLOCK-SEQUENCE-END token. + yaml_BLOCK_END_TOKEN // A BLOCK-END token. + + yaml_FLOW_SEQUENCE_START_TOKEN // A FLOW-SEQUENCE-START token. + yaml_FLOW_SEQUENCE_END_TOKEN // A FLOW-SEQUENCE-END token. + yaml_FLOW_MAPPING_START_TOKEN // A FLOW-MAPPING-START token. + yaml_FLOW_MAPPING_END_TOKEN // A FLOW-MAPPING-END token. + + yaml_BLOCK_ENTRY_TOKEN // A BLOCK-ENTRY token. + yaml_FLOW_ENTRY_TOKEN // A FLOW-ENTRY token. + yaml_KEY_TOKEN // A KEY token. + yaml_VALUE_TOKEN // A VALUE token. + + yaml_ALIAS_TOKEN // An ALIAS token. + yaml_ANCHOR_TOKEN // An ANCHOR token. + yaml_TAG_TOKEN // A TAG token. + yaml_SCALAR_TOKEN // A SCALAR token. +) + +func (tt yaml_token_type_t) String() string { + switch tt { + case yaml_NO_TOKEN: + return "yaml_NO_TOKEN" + case yaml_STREAM_START_TOKEN: + return "yaml_STREAM_START_TOKEN" + case yaml_STREAM_END_TOKEN: + return "yaml_STREAM_END_TOKEN" + case yaml_VERSION_DIRECTIVE_TOKEN: + return "yaml_VERSION_DIRECTIVE_TOKEN" + case yaml_TAG_DIRECTIVE_TOKEN: + return "yaml_TAG_DIRECTIVE_TOKEN" + case yaml_DOCUMENT_START_TOKEN: + return "yaml_DOCUMENT_START_TOKEN" + case yaml_DOCUMENT_END_TOKEN: + return "yaml_DOCUMENT_END_TOKEN" + case yaml_BLOCK_SEQUENCE_START_TOKEN: + return "yaml_BLOCK_SEQUENCE_START_TOKEN" + case yaml_BLOCK_MAPPING_START_TOKEN: + return "yaml_BLOCK_MAPPING_START_TOKEN" + case yaml_BLOCK_END_TOKEN: + return "yaml_BLOCK_END_TOKEN" + case yaml_FLOW_SEQUENCE_START_TOKEN: + return "yaml_FLOW_SEQUENCE_START_TOKEN" + case yaml_FLOW_SEQUENCE_END_TOKEN: + return "yaml_FLOW_SEQUENCE_END_TOKEN" + case yaml_FLOW_MAPPING_START_TOKEN: + return "yaml_FLOW_MAPPING_START_TOKEN" + case yaml_FLOW_MAPPING_END_TOKEN: + return "yaml_FLOW_MAPPING_END_TOKEN" + case yaml_BLOCK_ENTRY_TOKEN: + return "yaml_BLOCK_ENTRY_TOKEN" + case yaml_FLOW_ENTRY_TOKEN: + return "yaml_FLOW_ENTRY_TOKEN" + case yaml_KEY_TOKEN: + return "yaml_KEY_TOKEN" + case yaml_VALUE_TOKEN: + return "yaml_VALUE_TOKEN" + case yaml_ALIAS_TOKEN: + return "yaml_ALIAS_TOKEN" + case yaml_ANCHOR_TOKEN: + return "yaml_ANCHOR_TOKEN" + case yaml_TAG_TOKEN: + return "yaml_TAG_TOKEN" + case yaml_SCALAR_TOKEN: + return "yaml_SCALAR_TOKEN" + } + return "" +} + +// The token structure. +type yaml_token_t struct { + // The token type. + typ yaml_token_type_t + + // The start/end of the token. + start_mark, end_mark yaml_mark_t + + // The stream encoding (for yaml_STREAM_START_TOKEN). + encoding yaml_encoding_t + + // The alias/anchor/scalar value or tag/tag directive handle + // (for yaml_ALIAS_TOKEN, yaml_ANCHOR_TOKEN, yaml_SCALAR_TOKEN, yaml_TAG_TOKEN, yaml_TAG_DIRECTIVE_TOKEN). + value []byte + + // The tag suffix (for yaml_TAG_TOKEN). + suffix []byte + + // The tag directive prefix (for yaml_TAG_DIRECTIVE_TOKEN). + prefix []byte + + // The scalar style (for yaml_SCALAR_TOKEN). + style yaml_scalar_style_t + + // The version directive major/minor (for yaml_VERSION_DIRECTIVE_TOKEN). + major, minor int8 +} + +// Events + +type yaml_event_type_t int8 + +// Event types. +const ( + // An empty event. + yaml_NO_EVENT yaml_event_type_t = iota + + yaml_STREAM_START_EVENT // A STREAM-START event. + yaml_STREAM_END_EVENT // A STREAM-END event. + yaml_DOCUMENT_START_EVENT // A DOCUMENT-START event. + yaml_DOCUMENT_END_EVENT // A DOCUMENT-END event. + yaml_ALIAS_EVENT // An ALIAS event. + yaml_SCALAR_EVENT // A SCALAR event. + yaml_SEQUENCE_START_EVENT // A SEQUENCE-START event. + yaml_SEQUENCE_END_EVENT // A SEQUENCE-END event. + yaml_MAPPING_START_EVENT // A MAPPING-START event. + yaml_MAPPING_END_EVENT // A MAPPING-END event. +) + +// The event structure. +type yaml_event_t struct { + + // The event type. + typ yaml_event_type_t + + // The start and end of the event. + start_mark, end_mark yaml_mark_t + + // The document encoding (for yaml_STREAM_START_EVENT). + encoding yaml_encoding_t + + // The version directive (for yaml_DOCUMENT_START_EVENT). + version_directive *yaml_version_directive_t + + // The list of tag directives (for yaml_DOCUMENT_START_EVENT). + tag_directives []yaml_tag_directive_t + + // The anchor (for yaml_SCALAR_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT, yaml_ALIAS_EVENT). + anchor []byte + + // The tag (for yaml_SCALAR_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT). + tag []byte + + // The scalar value (for yaml_SCALAR_EVENT). + value []byte + + // Is the document start/end indicator implicit, or the tag optional? + // (for yaml_DOCUMENT_START_EVENT, yaml_DOCUMENT_END_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT, yaml_SCALAR_EVENT). + implicit bool + + // Is the tag optional for any non-plain style? (for yaml_SCALAR_EVENT). + quoted_implicit bool + + // The style (for yaml_SCALAR_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT). + style yaml_style_t +} + +func (e *yaml_event_t) scalar_style() yaml_scalar_style_t { return yaml_scalar_style_t(e.style) } +func (e *yaml_event_t) sequence_style() yaml_sequence_style_t { return yaml_sequence_style_t(e.style) } +func (e *yaml_event_t) mapping_style() yaml_mapping_style_t { return yaml_mapping_style_t(e.style) } + +// Nodes + +const ( + yaml_NULL_TAG = "tag:yaml.org,2002:null" // The tag !!null with the only possible value: null. + yaml_BOOL_TAG = "tag:yaml.org,2002:bool" // The tag !!bool with the values: true and false. + yaml_STR_TAG = "tag:yaml.org,2002:str" // The tag !!str for string values. + yaml_INT_TAG = "tag:yaml.org,2002:int" // The tag !!int for integer values. + yaml_FLOAT_TAG = "tag:yaml.org,2002:float" // The tag !!float for float values. + yaml_TIMESTAMP_TAG = "tag:yaml.org,2002:timestamp" // The tag !!timestamp for date and time values. + + yaml_SEQ_TAG = "tag:yaml.org,2002:seq" // The tag !!seq is used to denote sequences. + yaml_MAP_TAG = "tag:yaml.org,2002:map" // The tag !!map is used to denote mapping. + + // Not in original libyaml. + yaml_BINARY_TAG = "tag:yaml.org,2002:binary" + yaml_MERGE_TAG = "tag:yaml.org,2002:merge" + + yaml_DEFAULT_SCALAR_TAG = yaml_STR_TAG // The default scalar tag is !!str. + yaml_DEFAULT_SEQUENCE_TAG = yaml_SEQ_TAG // The default sequence tag is !!seq. + yaml_DEFAULT_MAPPING_TAG = yaml_MAP_TAG // The default mapping tag is !!map. +) + +type yaml_node_type_t int + +// Node types. +const ( + // An empty node. + yaml_NO_NODE yaml_node_type_t = iota + + yaml_SCALAR_NODE // A scalar node. + yaml_SEQUENCE_NODE // A sequence node. + yaml_MAPPING_NODE // A mapping node. +) + +// An element of a sequence node. +type yaml_node_item_t int + +// An element of a mapping node. +type yaml_node_pair_t struct { + key int // The key of the element. + value int // The value of the element. +} + +// The node structure. +type yaml_node_t struct { + typ yaml_node_type_t // The node type. + tag []byte // The node tag. + + // The node data. + + // The scalar parameters (for yaml_SCALAR_NODE). + scalar struct { + value []byte // The scalar value. + length int // The length of the scalar value. + style yaml_scalar_style_t // The scalar style. + } + + // The sequence parameters (for YAML_SEQUENCE_NODE). + sequence struct { + items_data []yaml_node_item_t // The stack of sequence items. + style yaml_sequence_style_t // The sequence style. + } + + // The mapping parameters (for yaml_MAPPING_NODE). + mapping struct { + pairs_data []yaml_node_pair_t // The stack of mapping pairs (key, value). + pairs_start *yaml_node_pair_t // The beginning of the stack. + pairs_end *yaml_node_pair_t // The end of the stack. + pairs_top *yaml_node_pair_t // The top of the stack. + style yaml_mapping_style_t // The mapping style. + } + + start_mark yaml_mark_t // The beginning of the node. + end_mark yaml_mark_t // The end of the node. + +} + +// The document structure. +type yaml_document_t struct { + + // The document nodes. + nodes []yaml_node_t + + // The version directive. + version_directive *yaml_version_directive_t + + // The list of tag directives. + tag_directives_data []yaml_tag_directive_t + tag_directives_start int // The beginning of the tag directives list. + tag_directives_end int // The end of the tag directives list. + + start_implicit int // Is the document start indicator implicit? + end_implicit int // Is the document end indicator implicit? + + // The start/end of the document. + start_mark, end_mark yaml_mark_t +} + +// The prototype of a read handler. +// +// The read handler is called when the parser needs to read more bytes from the +// source. The handler should write not more than size bytes to the buffer. +// The number of written bytes should be set to the size_read variable. +// +// [in,out] data A pointer to an application data specified by +// yaml_parser_set_input(). +// [out] buffer The buffer to write the data from the source. +// [in] size The size of the buffer. +// [out] size_read The actual number of bytes read from the source. +// +// On success, the handler should return 1. If the handler failed, +// the returned value should be 0. On EOF, the handler should set the +// size_read to 0 and return 1. +type yaml_read_handler_t func(parser *yaml_parser_t, buffer []byte) (n int, err error) + +// This structure holds information about a potential simple key. +type yaml_simple_key_t struct { + possible bool // Is a simple key possible? + required bool // Is a simple key required? + token_number int // The number of the token. + mark yaml_mark_t // The position mark. +} + +// The states of the parser. +type yaml_parser_state_t int + +const ( + yaml_PARSE_STREAM_START_STATE yaml_parser_state_t = iota + + yaml_PARSE_IMPLICIT_DOCUMENT_START_STATE // Expect the beginning of an implicit document. + yaml_PARSE_DOCUMENT_START_STATE // Expect DOCUMENT-START. + yaml_PARSE_DOCUMENT_CONTENT_STATE // Expect the content of a document. + yaml_PARSE_DOCUMENT_END_STATE // Expect DOCUMENT-END. + yaml_PARSE_BLOCK_NODE_STATE // Expect a block node. + yaml_PARSE_BLOCK_NODE_OR_INDENTLESS_SEQUENCE_STATE // Expect a block node or indentless sequence. + yaml_PARSE_FLOW_NODE_STATE // Expect a flow node. + yaml_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE // Expect the first entry of a block sequence. + yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE // Expect an entry of a block sequence. + yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE // Expect an entry of an indentless sequence. + yaml_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE // Expect the first key of a block mapping. + yaml_PARSE_BLOCK_MAPPING_KEY_STATE // Expect a block mapping key. + yaml_PARSE_BLOCK_MAPPING_VALUE_STATE // Expect a block mapping value. + yaml_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE // Expect the first entry of a flow sequence. + yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE // Expect an entry of a flow sequence. + yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE // Expect a key of an ordered mapping. + yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE // Expect a value of an ordered mapping. + yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE // Expect the and of an ordered mapping entry. + yaml_PARSE_FLOW_MAPPING_FIRST_KEY_STATE // Expect the first key of a flow mapping. + yaml_PARSE_FLOW_MAPPING_KEY_STATE // Expect a key of a flow mapping. + yaml_PARSE_FLOW_MAPPING_VALUE_STATE // Expect a value of a flow mapping. + yaml_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE // Expect an empty value of a flow mapping. + yaml_PARSE_END_STATE // Expect nothing. +) + +func (ps yaml_parser_state_t) String() string { + switch ps { + case yaml_PARSE_STREAM_START_STATE: + return "yaml_PARSE_STREAM_START_STATE" + case yaml_PARSE_IMPLICIT_DOCUMENT_START_STATE: + return "yaml_PARSE_IMPLICIT_DOCUMENT_START_STATE" + case yaml_PARSE_DOCUMENT_START_STATE: + return "yaml_PARSE_DOCUMENT_START_STATE" + case yaml_PARSE_DOCUMENT_CONTENT_STATE: + return "yaml_PARSE_DOCUMENT_CONTENT_STATE" + case yaml_PARSE_DOCUMENT_END_STATE: + return "yaml_PARSE_DOCUMENT_END_STATE" + case yaml_PARSE_BLOCK_NODE_STATE: + return "yaml_PARSE_BLOCK_NODE_STATE" + case yaml_PARSE_BLOCK_NODE_OR_INDENTLESS_SEQUENCE_STATE: + return "yaml_PARSE_BLOCK_NODE_OR_INDENTLESS_SEQUENCE_STATE" + case yaml_PARSE_FLOW_NODE_STATE: + return "yaml_PARSE_FLOW_NODE_STATE" + case yaml_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE: + return "yaml_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE" + case yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE: + return "yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE" + case yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE: + return "yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE" + case yaml_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE: + return "yaml_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE" + case yaml_PARSE_BLOCK_MAPPING_KEY_STATE: + return "yaml_PARSE_BLOCK_MAPPING_KEY_STATE" + case yaml_PARSE_BLOCK_MAPPING_VALUE_STATE: + return "yaml_PARSE_BLOCK_MAPPING_VALUE_STATE" + case yaml_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE: + return "yaml_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE" + case yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE: + return "yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE" + case yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE: + return "yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE" + case yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE: + return "yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE" + case yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE: + return "yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE" + case yaml_PARSE_FLOW_MAPPING_FIRST_KEY_STATE: + return "yaml_PARSE_FLOW_MAPPING_FIRST_KEY_STATE" + case yaml_PARSE_FLOW_MAPPING_KEY_STATE: + return "yaml_PARSE_FLOW_MAPPING_KEY_STATE" + case yaml_PARSE_FLOW_MAPPING_VALUE_STATE: + return "yaml_PARSE_FLOW_MAPPING_VALUE_STATE" + case yaml_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE: + return "yaml_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE" + case yaml_PARSE_END_STATE: + return "yaml_PARSE_END_STATE" + } + return "" +} + +// This structure holds aliases data. +type yaml_alias_data_t struct { + anchor []byte // The anchor. + index int // The node id. + mark yaml_mark_t // The anchor mark. +} + +// The parser structure. +// +// All members are internal. Manage the structure using the +// yaml_parser_ family of functions. +type yaml_parser_t struct { + + // Error handling + + error yaml_error_type_t // Error type. + + problem string // Error description. + + // The byte about which the problem occured. + problem_offset int + problem_value int + problem_mark yaml_mark_t + + // The error context. + context string + context_mark yaml_mark_t + + // Reader stuff + + read_handler yaml_read_handler_t // Read handler. + + input_file io.Reader // File input data. + input []byte // String input data. + input_pos int + + eof bool // EOF flag + + buffer []byte // The working buffer. + buffer_pos int // The current position of the buffer. + + unread int // The number of unread characters in the buffer. + + raw_buffer []byte // The raw buffer. + raw_buffer_pos int // The current position of the buffer. + + encoding yaml_encoding_t // The input encoding. + + offset int // The offset of the current position (in bytes). + mark yaml_mark_t // The mark of the current position. + + // Scanner stuff + + stream_start_produced bool // Have we started to scan the input stream? + stream_end_produced bool // Have we reached the end of the input stream? + + flow_level int // The number of unclosed '[' and '{' indicators. + + tokens []yaml_token_t // The tokens queue. + tokens_head int // The head of the tokens queue. + tokens_parsed int // The number of tokens fetched from the queue. + token_available bool // Does the tokens queue contain a token ready for dequeueing. + + indent int // The current indentation level. + indents []int // The indentation levels stack. + + simple_key_allowed bool // May a simple key occur at the current position? + simple_keys []yaml_simple_key_t // The stack of simple keys. + + // Parser stuff + + state yaml_parser_state_t // The current parser state. + states []yaml_parser_state_t // The parser states stack. + marks []yaml_mark_t // The stack of marks. + tag_directives []yaml_tag_directive_t // The list of TAG directives. + + // Dumper stuff + + aliases []yaml_alias_data_t // The alias data. + + document *yaml_document_t // The currently parsed document. +} + +// Emitter Definitions + +// The prototype of a write handler. +// +// The write handler is called when the emitter needs to flush the accumulated +// characters to the output. The handler should write @a size bytes of the +// @a buffer to the output. +// +// @param[in,out] data A pointer to an application data specified by +// yaml_emitter_set_output(). +// @param[in] buffer The buffer with bytes to be written. +// @param[in] size The size of the buffer. +// +// @returns On success, the handler should return @c 1. If the handler failed, +// the returned value should be @c 0. +// +type yaml_write_handler_t func(emitter *yaml_emitter_t, buffer []byte) error + +type yaml_emitter_state_t int + +// The emitter states. +const ( + // Expect STREAM-START. + yaml_EMIT_STREAM_START_STATE yaml_emitter_state_t = iota + + yaml_EMIT_FIRST_DOCUMENT_START_STATE // Expect the first DOCUMENT-START or STREAM-END. + yaml_EMIT_DOCUMENT_START_STATE // Expect DOCUMENT-START or STREAM-END. + yaml_EMIT_DOCUMENT_CONTENT_STATE // Expect the content of a document. + yaml_EMIT_DOCUMENT_END_STATE // Expect DOCUMENT-END. + yaml_EMIT_FLOW_SEQUENCE_FIRST_ITEM_STATE // Expect the first item of a flow sequence. + yaml_EMIT_FLOW_SEQUENCE_ITEM_STATE // Expect an item of a flow sequence. + yaml_EMIT_FLOW_MAPPING_FIRST_KEY_STATE // Expect the first key of a flow mapping. + yaml_EMIT_FLOW_MAPPING_KEY_STATE // Expect a key of a flow mapping. + yaml_EMIT_FLOW_MAPPING_SIMPLE_VALUE_STATE // Expect a value for a simple key of a flow mapping. + yaml_EMIT_FLOW_MAPPING_VALUE_STATE // Expect a value of a flow mapping. + yaml_EMIT_BLOCK_SEQUENCE_FIRST_ITEM_STATE // Expect the first item of a block sequence. + yaml_EMIT_BLOCK_SEQUENCE_ITEM_STATE // Expect an item of a block sequence. + yaml_EMIT_BLOCK_MAPPING_FIRST_KEY_STATE // Expect the first key of a block mapping. + yaml_EMIT_BLOCK_MAPPING_KEY_STATE // Expect the key of a block mapping. + yaml_EMIT_BLOCK_MAPPING_SIMPLE_VALUE_STATE // Expect a value for a simple key of a block mapping. + yaml_EMIT_BLOCK_MAPPING_VALUE_STATE // Expect a value of a block mapping. + yaml_EMIT_END_STATE // Expect nothing. +) + +// The emitter structure. +// +// All members are internal. Manage the structure using the @c yaml_emitter_ +// family of functions. +type yaml_emitter_t struct { + + // Error handling + + error yaml_error_type_t // Error type. + problem string // Error description. + + // Writer stuff + + write_handler yaml_write_handler_t // Write handler. + + output_buffer *[]byte // String output data. + output_file io.Writer // File output data. + + buffer []byte // The working buffer. + buffer_pos int // The current position of the buffer. + + raw_buffer []byte // The raw buffer. + raw_buffer_pos int // The current position of the buffer. + + encoding yaml_encoding_t // The stream encoding. + + // Emitter stuff + + canonical bool // If the output is in the canonical style? + best_indent int // The number of indentation spaces. + best_width int // The preferred width of the output lines. + unicode bool // Allow unescaped non-ASCII characters? + line_break yaml_break_t // The preferred line break. + + state yaml_emitter_state_t // The current emitter state. + states []yaml_emitter_state_t // The stack of states. + + events []yaml_event_t // The event queue. + events_head int // The head of the event queue. + + indents []int // The stack of indentation levels. + + tag_directives []yaml_tag_directive_t // The list of tag directives. + + indent int // The current indentation level. + + flow_level int // The current flow level. + + root_context bool // Is it the document root context? + sequence_context bool // Is it a sequence context? + mapping_context bool // Is it a mapping context? + simple_key_context bool // Is it a simple mapping key context? + + line int // The current line. + column int // The current column. + whitespace bool // If the last character was a whitespace? + indention bool // If the last character was an indentation character (' ', '-', '?', ':')? + open_ended bool // If an explicit document end is required? + + // Anchor analysis. + anchor_data struct { + anchor []byte // The anchor value. + alias bool // Is it an alias? + } + + // Tag analysis. + tag_data struct { + handle []byte // The tag handle. + suffix []byte // The tag suffix. + } + + // Scalar analysis. + scalar_data struct { + value []byte // The scalar value. + multiline bool // Does the scalar contain line breaks? + flow_plain_allowed bool // Can the scalar be expessed in the flow plain style? + block_plain_allowed bool // Can the scalar be expressed in the block plain style? + single_quoted_allowed bool // Can the scalar be expressed in the single quoted style? + block_allowed bool // Can the scalar be expressed in the literal or folded styles? + style yaml_scalar_style_t // The output style. + } + + // Dumper stuff + + opened bool // If the stream was already opened? + closed bool // If the stream was already closed? + + // The information associated with the document nodes. + anchors *struct { + references int // The number of references. + anchor int // The anchor id. + serialized bool // If the node has been emitted? + } + + last_anchor_id int // The last assigned anchor id. + + document *yaml_document_t // The currently emitted document. +} diff --git a/vendor/gopkg.in/yaml.v2/yamlprivateh.go b/vendor/gopkg.in/yaml.v2/yamlprivateh.go new file mode 100644 index 000000000..8110ce3c3 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/yamlprivateh.go @@ -0,0 +1,173 @@ +package yaml + +const ( + // The size of the input raw buffer. + input_raw_buffer_size = 512 + + // The size of the input buffer. + // It should be possible to decode the whole raw buffer. + input_buffer_size = input_raw_buffer_size * 3 + + // The size of the output buffer. + output_buffer_size = 128 + + // The size of the output raw buffer. + // It should be possible to encode the whole output buffer. + output_raw_buffer_size = (output_buffer_size*2 + 2) + + // The size of other stacks and queues. + initial_stack_size = 16 + initial_queue_size = 16 + initial_string_size = 16 +) + +// Check if the character at the specified position is an alphabetical +// character, a digit, '_', or '-'. +func is_alpha(b []byte, i int) bool { + return b[i] >= '0' && b[i] <= '9' || b[i] >= 'A' && b[i] <= 'Z' || b[i] >= 'a' && b[i] <= 'z' || b[i] == '_' || b[i] == '-' +} + +// Check if the character at the specified position is a digit. +func is_digit(b []byte, i int) bool { + return b[i] >= '0' && b[i] <= '9' +} + +// Get the value of a digit. +func as_digit(b []byte, i int) int { + return int(b[i]) - '0' +} + +// Check if the character at the specified position is a hex-digit. +func is_hex(b []byte, i int) bool { + return b[i] >= '0' && b[i] <= '9' || b[i] >= 'A' && b[i] <= 'F' || b[i] >= 'a' && b[i] <= 'f' +} + +// Get the value of a hex-digit. +func as_hex(b []byte, i int) int { + bi := b[i] + if bi >= 'A' && bi <= 'F' { + return int(bi) - 'A' + 10 + } + if bi >= 'a' && bi <= 'f' { + return int(bi) - 'a' + 10 + } + return int(bi) - '0' +} + +// Check if the character is ASCII. +func is_ascii(b []byte, i int) bool { + return b[i] <= 0x7F +} + +// Check if the character at the start of the buffer can be printed unescaped. +func is_printable(b []byte, i int) bool { + return ((b[i] == 0x0A) || // . == #x0A + (b[i] >= 0x20 && b[i] <= 0x7E) || // #x20 <= . <= #x7E + (b[i] == 0xC2 && b[i+1] >= 0xA0) || // #0xA0 <= . <= #xD7FF + (b[i] > 0xC2 && b[i] < 0xED) || + (b[i] == 0xED && b[i+1] < 0xA0) || + (b[i] == 0xEE) || + (b[i] == 0xEF && // #xE000 <= . <= #xFFFD + !(b[i+1] == 0xBB && b[i+2] == 0xBF) && // && . != #xFEFF + !(b[i+1] == 0xBF && (b[i+2] == 0xBE || b[i+2] == 0xBF)))) +} + +// Check if the character at the specified position is NUL. +func is_z(b []byte, i int) bool { + return b[i] == 0x00 +} + +// Check if the beginning of the buffer is a BOM. +func is_bom(b []byte, i int) bool { + return b[0] == 0xEF && b[1] == 0xBB && b[2] == 0xBF +} + +// Check if the character at the specified position is space. +func is_space(b []byte, i int) bool { + return b[i] == ' ' +} + +// Check if the character at the specified position is tab. +func is_tab(b []byte, i int) bool { + return b[i] == '\t' +} + +// Check if the character at the specified position is blank (space or tab). +func is_blank(b []byte, i int) bool { + //return is_space(b, i) || is_tab(b, i) + return b[i] == ' ' || b[i] == '\t' +} + +// Check if the character at the specified position is a line break. +func is_break(b []byte, i int) bool { + return (b[i] == '\r' || // CR (#xD) + b[i] == '\n' || // LF (#xA) + b[i] == 0xC2 && b[i+1] == 0x85 || // NEL (#x85) + b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA8 || // LS (#x2028) + b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA9) // PS (#x2029) +} + +func is_crlf(b []byte, i int) bool { + return b[i] == '\r' && b[i+1] == '\n' +} + +// Check if the character is a line break or NUL. +func is_breakz(b []byte, i int) bool { + //return is_break(b, i) || is_z(b, i) + return ( // is_break: + b[i] == '\r' || // CR (#xD) + b[i] == '\n' || // LF (#xA) + b[i] == 0xC2 && b[i+1] == 0x85 || // NEL (#x85) + b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA8 || // LS (#x2028) + b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA9 || // PS (#x2029) + // is_z: + b[i] == 0) +} + +// Check if the character is a line break, space, or NUL. +func is_spacez(b []byte, i int) bool { + //return is_space(b, i) || is_breakz(b, i) + return ( // is_space: + b[i] == ' ' || + // is_breakz: + b[i] == '\r' || // CR (#xD) + b[i] == '\n' || // LF (#xA) + b[i] == 0xC2 && b[i+1] == 0x85 || // NEL (#x85) + b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA8 || // LS (#x2028) + b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA9 || // PS (#x2029) + b[i] == 0) +} + +// Check if the character is a line break, space, tab, or NUL. +func is_blankz(b []byte, i int) bool { + //return is_blank(b, i) || is_breakz(b, i) + return ( // is_blank: + b[i] == ' ' || b[i] == '\t' || + // is_breakz: + b[i] == '\r' || // CR (#xD) + b[i] == '\n' || // LF (#xA) + b[i] == 0xC2 && b[i+1] == 0x85 || // NEL (#x85) + b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA8 || // LS (#x2028) + b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA9 || // PS (#x2029) + b[i] == 0) +} + +// Determine the width of the character. +func width(b byte) int { + // Don't replace these by a switch without first + // confirming that it is being inlined. + if b&0x80 == 0x00 { + return 1 + } + if b&0xE0 == 0xC0 { + return 2 + } + if b&0xF0 == 0xE0 { + return 3 + } + if b&0xF8 == 0xF0 { + return 4 + } + return 0 + +} diff --git a/vendor/vendor.json b/vendor/vendor.json index a1ed690cf..cc9aa0322 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -2,6 +2,12 @@ "comment": "", "ignore": "test", "package": [ + { + "checksumSHA1": "rK3ght7KTtHGdm0V4+U7fv9+tUU=", + "path": "github.com/Azure/azure-sdk-for-go/storage", + "revision": "8e625d1702a32d01cef05a9252198d231c4af113", + "revisionTime": "2017-02-08T01:01:20Z" + }, { "path": "github.com/Sirupsen/logrus", "revision": "32055c351ea8b00b96d70f28db48d9840feaf0ec", @@ -62,6 +68,18 @@ "revision": "42c364ba490082e4815b5222728711b3440603eb", "revisionTime": "2017-01-13T15:16:12Z" }, + { + "checksumSHA1": "2UmMbNHc8FBr98mJFN1k8ISOIHk=", + "path": "github.com/garyburd/redigo/internal", + "revision": "0d253a66e6e1349f4581d6d2b300ee434ee2da9f", + "revisionTime": "2017-02-16T21:49:44Z" + }, + { + "checksumSHA1": "81OSg/NapmTaRpSS+oYsPVE0b1Y=", + "path": "github.com/garyburd/redigo/redis", + "revision": "0d253a66e6e1349f4581d6d2b300ee434ee2da9f", + "revisionTime": "2017-02-16T21:49:44Z" + }, { "checksumSHA1": "3yco0089CSJ4qbyUccpbDC2+dPg=", "path": "github.com/gogo/protobuf/gogoproto", @@ -163,10 +181,10 @@ "revisionTime": "2016-07-23T06:10:19Z" }, { - "checksumSHA1": "iDDGi0/U33hxoaQBgM3ww882lmU=", + "checksumSHA1": "fUWokilZyc1QDKnIgCDJE8n1S9U=", "path": "github.com/minio/cli", - "revision": "cea7bbb0e52ac4d24c1de3f450545f38246075a2", - "revisionTime": "2017-02-15T09:44:04Z" + "revision": "b8ae5507c0ceceecc22d5dbd386b58fbd4fdce72", + "revisionTime": "2017-02-27T07:32:28Z" }, { "checksumSHA1": "NBGyq2+iTtJvJ+ElG4FzHLe1WSY=", @@ -208,12 +226,6 @@ "revision": "9e734013294ab153b0bdbe182738bcddd46f1947", "revisionTime": "2016-08-18T00:31:20Z" }, - { - "checksumSHA1": "GOSe2XEQI4AYwrMoLZu8vtmzkJM=", - "path": "github.com/minio/redigo/redis", - "revision": "5e2117cd32d677a36dcd8c9c83776a065555653b", - "revisionTime": "2016-07-24T00:05:56Z" - }, { "checksumSHA1": "URVle4qtadmW9w9BulDRHY3kxnA=", "path": "github.com/minio/sha256-simd", @@ -296,10 +308,10 @@ "revisionTime": "2016-03-11T21:55:03Z" }, { - "checksumSHA1": "+Pcohsuq0Mi/y8bgaDFjb/CGzkk=", + "checksumSHA1": "k/Xh0p5L7+tBCXAL2dOCwUf9J3Y=", "path": "github.com/tidwall/gjson", - "revision": "7c631e98686a791e5fc60ff099512968122afb52", - "revisionTime": "2016-09-08T16:02:40Z" + "revision": "09d1c5c5bc64e094394dfe2150220d906c55ac37", + "revisionTime": "2017-02-05T16:10:42Z" }, { "checksumSHA1": "qmePMXEDYGwkAfT9QvtMC58JN/E=", @@ -350,6 +362,12 @@ "path": "gopkg.in/olivere/elastic.v3/uritemplates", "revision": "f7ae701daf3abe5dfb99f57b3f47738ec93c9c26", "revisionTime": "2016-07-16T10:42:39Z" + }, + { + "checksumSHA1": "12GqsW8PiRPnezDDy0v4brZrndM=", + "path": "gopkg.in/yaml.v2", + "revision": "a5b47d31c556af34a302ce5d659e6fea44d90de0", + "revisionTime": "2016-09-28T15:37:09Z" } ], "rootPath": "github.com/minio/minio"