From 4c63fd06c6df4a5a5de1fa2917a47be75a897e10 Mon Sep 17 00:00:00 2001 From: Nitish Tiwari Date: Thu, 4 May 2017 19:30:46 -0700 Subject: [PATCH 01/80] Add Kubernetes yaml file deployment example (#4262) * Add Kubernetes yaml file deployment example * Change the Docker image tag to latest * Update latest release tag --- docs/orchestration/kubernetes-yaml/README.md | 337 ++++++++++++++++++ .../minio-distributed-headless-service.yaml | 13 + .../minio-distributed-service.yaml | 12 + .../minio-distributed-statefulset.yaml | 49 +++ .../minio-standalone-deployment.yaml | 40 +++ .../kubernetes-yaml/minio-standalone-pvc.yaml | 17 + .../minio-standalone-service.yaml | 12 + docs/orchestration/kubernetes/README.md | 2 +- 8 files changed, 481 insertions(+), 1 deletion(-) create mode 100644 docs/orchestration/kubernetes-yaml/README.md create mode 100644 docs/orchestration/kubernetes-yaml/minio-distributed-headless-service.yaml create mode 100644 docs/orchestration/kubernetes-yaml/minio-distributed-service.yaml create mode 100644 docs/orchestration/kubernetes-yaml/minio-distributed-statefulset.yaml create mode 100644 docs/orchestration/kubernetes-yaml/minio-standalone-deployment.yaml create mode 100644 docs/orchestration/kubernetes-yaml/minio-standalone-pvc.yaml create mode 100644 docs/orchestration/kubernetes-yaml/minio-standalone-service.yaml diff --git a/docs/orchestration/kubernetes-yaml/README.md b/docs/orchestration/kubernetes-yaml/README.md new file mode 100644 index 000000000..20451a2f7 --- /dev/null +++ b/docs/orchestration/kubernetes-yaml/README.md @@ -0,0 +1,337 @@ +# Cloud Native Deployment of 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) + +## Table of Contents + +- [Prerequisites](#prerequisites) +- [Minio Standalone Server Deployment](#minio-standalone-server-deployment) + - [Standalone Quickstart](#standalone-quickstart) + - [Step 1: Create Persistent Volume Claim](#step-1-create-persistent-volume-claim) + - [Step 2: Create Deployment](#step-2-create-minio-deployment) + - [Step 3: Create LoadBalancer Service](#step-3-create-minio-service) + - [Step 4: Resource cleanup](#step-4-resource-cleanup) +- [Minio Distributed Server Deployment](#minio-distributed-server-deployment) + - [Distributed Quickstart](#distributed-quickstart) + - [Step 1: Create Minio Headless Service](#step-1-create-minio-headless-service) + - [Step 2: Create Minio Statefulset](#step-2-create-minio-statefulset) + - [Step 3: Create LoadBalancer Service](#step-3-create-minio-service) + - [Step 4: Resource cleanup](#step-4-resource-cleanup) + +## Prerequisites + +To run this example, you need Kubernetes version >=1.4 cluster installed and running, and that you have installed the [`kubectl`](https://kubernetes.io/docs/tasks/kubectl/install/) command line tool in your path. Please see the +[getting started guides](https://kubernetes.io/docs/getting-started-guides/) for installation instructions for your platform. + +## Minio Standalone Server Deployment + +The following section describes the process to deploy standalone [Minio](https://minio.io/) server on Kubernetes. The deployment uses the [official Minio Docker image](https://hub.docker.com/r/minio/minio/~/dockerfile/) from Docker Hub. + +This section uses following core components of Kubernetes: + +- [_Pods_](https://kubernetes.io/docs/user-guide/pods/) +- [_Services_](https://kubernetes.io/docs/user-guide/services/) +- [_Deployments_](https://kubernetes.io/docs/user-guide/deployments/) +- [_Persistent Volume Claims_](https://kubernetes.io/docs/user-guide/persistent-volumes/#persistentvolumeclaims) + +### Standalone Quickstart + +Run the below commands to get started quickly + +```sh +kubectl create -f https://github.com/minio/minio/blob/master/docs/orchestration/kubernetes-yaml/minio-standalone-pvc.yaml?raw=true +kubectl create -f https://github.com/minio/minio/blob/master/docs/orchestration/kubernetes-yaml/minio-standalone-deployment.yaml?raw=true +kubectl create -f https://github.com/minio/minio/blob/master/docs/orchestration/kubernetes-yaml/minio-standalone-service.yaml?raw=true +``` + +### Step 1: Create Persistent Volume Claim + +Minio needs persistent storage to store objects. If there is no +persistent storage, the data stored in Minio instance will be stored in the container file system and will be wiped off as soon as the container restarts. + +Create a persistent volume claim (PVC) to request storage for the Minio instance. Kubernetes looks out for PVs matching the PVC request in the cluster and binds it to the PVC automatically. + +This is the PVC description. + +```sh +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + # This name uniquely identifies the PVC. Will be used in deployment below. + name: minio-pv-claim + annotations: + volume.alpha.kubernetes.io/storage-class: anything + labels: + app: minio-storage-claim +spec: + # Read more about access modes here: http://kubernetes.io/docs/user-guide/persistent-volumes/#access-modes + accessModes: + - ReadWriteOnce + resources: + # This is the request for storage. Should be available in the cluster. + requests: + storage: 10Gi +``` + +Create the PersistentVolumeClaim + +```sh +kubectl create -f https://github.com/minio/minio/blob/master/docs/orchestration/kubernetes-yaml/minio-standalone-pvc.yaml?raw=true +persistentvolumeclaim "minio-pv-claim" created +``` + +### Step 2: Create Minio Deployment + +A deployment encapsulates replica sets and pods — so, if a pod goes down, replication controller makes sure another pod comes up automatically. This way you won’t need to bother about pod failures and will have a stable Minio service available. + +This is the deployment description. + +```sh +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + # This name uniquely identifies the Deployment + name: minio-deployment +spec: + strategy: + type: Recreate + template: + metadata: + labels: + # Label is used as selector in the service. + app: minio + spec: + # Refer to the PVC created earlier + volumes: + - name: storage + persistentVolumeClaim: + # Name of the PVC created earlier + claimName: minio-pv-claim + containers: + - name: minio + # Pulls the default Minio image from Docker Hub + image: minio/minio:RELEASE.2017-05-05T01-14-51Z + args: + - server + - /storage + env: + # Minio access key and secret key + - name: MINIO_ACCESS_KEY + value: "minio" + - name: MINIO_SECRET_KEY + value: "minio123" + ports: + - containerPort: 9000 + hostPort: 9000 + # Mount the volume into the pod + volumeMounts: + - name: storage # must match the volume name, above + mountPath: "/storage" +``` + +Create the Deployment + +```sh +kubectl create -f https://github.com/minio/minio/blob/master/docs/orchestration/kubernetes-yaml/minio-standalone-deployment.yaml?raw=true +deployment "minio-deployment" created +``` + +### Step 3: Create Minio Service + +Now that you have a Minio deployment running, you may either want to access it internally (within the cluster) or expose it as a Service onto an external (outside of your cluster, maybe public internet) IP address, depending on your use case. You can achieve this using Services. There are 3 major service types — default type is ClusterIP, which exposes a service to connection from inside the cluster. NodePort and LoadBalancer are two types that expose services to external traffic. + +In this example, we expose the Minio Deployment by creating a LoadBalancer service. This is the service description. + +```sh +apiVersion: v1 +kind: Service +metadata: + name: minio-service +spec: + type: LoadBalancer + ports: + - port: 9000 + targetPort: 9000 + protocol: TCP + selector: + app: minio +``` +Create the Minio service + +```sh +kubectl create -f https://github.com/minio/minio/blob/master/docs/orchestration/kubernetes-yaml/minio-standalone-service.yaml?raw=true +service "minio-service" created +``` + +The `LoadBalancer` service takes couple of minutes to launch. To check if the service was created successfully, run the command + +```sh +kubectl get svc minio-service +NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE +minio-service 10.55.248.23 104.199.249.165 9000:31852/TCP 1m +``` + +### Step 4: Resource cleanup + +Once you are done, cleanup the cluster using +```sh +kubectl delete deployment minio-deployment \ +&& kubectl delete pvc minio-pv-claim \ +&& kubectl delete svc minio-service +``` + +## Minio Distributed Server Deployment + +The following document describes the process to deploy [distributed Minio](https://docs.minio.io/docs/distributed-minio-quickstart-guide) server on Kubernetes. This example uses the [official Minio Docker image](https://hub.docker.com/r/minio/minio/~/dockerfile/) from Docker Hub. + +This example uses following core components of Kubernetes: + +- [_Pods_](https://kubernetes.io/docs/concepts/workloads/pods/pod/) +- [_Services_](https://kubernetes.io/docs/concepts/services-networking/service/) +- [_Statefulsets_](https://kubernetes.io/docs/tutorials/stateful-application/basic-stateful-set/) + +### Distributed Quickstart + +Run the below commands to get started quickly + +```sh +kubectl create -f https://github.com/minio/minio/blob/master/docs/orchestration/kubernetes-yaml/minio-distributed-headless-service.yaml?raw=true +kubectl create -f https://github.com/minio/minio/blob/master/docs/orchestration/kubernetes-yaml/minio-distributed-statefulset.yaml?raw=true +kubectl create -f https://github.com/minio/minio/blob/master/docs/orchestration/kubernetes-yaml/minio-distributed-service.yaml?raw=true +``` + +### Step 1: Create Minio Headless Service + +Headless Service controls the domain within which StatefulSets are created. The domain managed by this Service takes the form: `$(service name).$(namespace).svc.cluster.local` (where “cluster.local” is the cluster domain), and the pods in this domain take the form: `$(pod-name-{i}).$(service name).$(namespace).svc.cluster.local`. This is required to get a DNS resolvable URL for each of the pods created within the Statefulset. + +This is the Headless service description. + +```sh +apiVersion: v1 +kind: Service +metadata: + name: minio + labels: + app: minio +spec: + clusterIP: None + ports: + - port: 9000 + name: minio + selector: + app: minio +``` + +Create the Headless Service + +```sh +$ kubectl create -f https://github.com/minio/minio/blob/master/docs/orchestration/kubernetes-yaml/minio-distributed-headless-service.yaml?raw=true +service "minio" created +``` + +### Step 2: Create Minio Statefulset + +A StatefulSet provides a deterministic name and a unique identity to each pod, making it easy to deploy stateful distributed applications. To launch distributed Minio you 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 pods. StatefulSets offer a perfect way to handle this requirement. + +This is the Statefulset description. + +```sh +apiVersion: apps/v1beta1 +kind: StatefulSet +metadata: + name: minio +spec: + serviceName: minio + replicas: 4 + template: + metadata: + annotations: + pod.alpha.kubernetes.io/initialized: "true" + labels: + app: minio + spec: + containers: + - name: minio + env: + - name: MINIO_ACCESS_KEY + value: "minio" + - name: MINIO_SECRET_KEY + value: "minio123" + image: minio/minio:RELEASE.2017-05-05T01-14-51Z + args: + - server + - http://minio-0.minio.default.svc.cluster.local/data + - http://minio-1.minio.default.svc.cluster.local/data + - http://minio-2.minio.default.svc.cluster.local/data + - http://minio-3.minio.default.svc.cluster.local/data + ports: + - containerPort: 9000 + hostPort: 9000 + # These volume mounts are persistent. Each pod in the PetSet + # gets a volume mounted based on this field. + volumeMounts: + - name: data + mountPath: /data + # These are converted to volume claims by the controller + # and mounted at the paths mentioned above. + volumeClaimTemplates: + - metadata: + name: data + annotations: + volume.alpha.kubernetes.io/storage-class: anything + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 10Gi +``` + +Create the Statefulset + +```sh +$ kubectl create -f https://github.com/minio/minio/blob/master/docs/orchestration/kubernetes-yaml/minio-distributed-statefulset.yaml?raw=true +statefulset "minio" created +``` + +### Step 3: Create Minio Service + +Now that you have a Minio statefulset running, you may either want to access it internally (within the cluster) or expose it as a Service onto an external (outside of your cluster, maybe public internet) IP address, depending on your use case. You can achieve this using Services. There are 3 major service types — default type is ClusterIP, which exposes a service to connection from inside the cluster. NodePort and LoadBalancer are two types that expose services to external traffic. + +In this example, we expose the Minio Deployment by creating a LoadBalancer service. This is the service description. + +```sh +apiVersion: v1 +kind: Service +metadata: + name: minio-service +spec: + type: LoadBalancer + ports: + - port: 9000 + targetPort: 9000 + protocol: TCP + selector: + app: minio +``` +Create the Minio service + +```sh +$ kubectl create -f https://github.com/minio/minio/blob/master/docs/orchestration/kubernetes-yaml/minio-distributed-service.yaml?raw=true +service "minio-service" created +``` + +The `LoadBalancer` service takes couple of minutes to launch. To check if the service was created successfully, run the command + +```sh +$ kubectl get svc minio-service +NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE +minio-service 10.55.248.23 104.199.249.165 9000:31852/TCP 1m +``` + +### Step 4: Resource cleanup + +You can cleanup the cluster using +```sh +kubectl delete statefulset minio \ +&& kubectl delete svc minio \ +&& kubectl delete svc minio-service +``` diff --git a/docs/orchestration/kubernetes-yaml/minio-distributed-headless-service.yaml b/docs/orchestration/kubernetes-yaml/minio-distributed-headless-service.yaml new file mode 100644 index 000000000..a822d76eb --- /dev/null +++ b/docs/orchestration/kubernetes-yaml/minio-distributed-headless-service.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Service +metadata: + name: minio + labels: + app: minio +spec: + clusterIP: None + ports: + - port: 9000 + name: minio + selector: + app: minio diff --git a/docs/orchestration/kubernetes-yaml/minio-distributed-service.yaml b/docs/orchestration/kubernetes-yaml/minio-distributed-service.yaml new file mode 100644 index 000000000..60514a863 --- /dev/null +++ b/docs/orchestration/kubernetes-yaml/minio-distributed-service.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Service +metadata: + name: minio-service +spec: + type: LoadBalancer + ports: + - port: 9000 + targetPort: 9000 + protocol: TCP + selector: + app: minio diff --git a/docs/orchestration/kubernetes-yaml/minio-distributed-statefulset.yaml b/docs/orchestration/kubernetes-yaml/minio-distributed-statefulset.yaml new file mode 100644 index 000000000..08aadeebc --- /dev/null +++ b/docs/orchestration/kubernetes-yaml/minio-distributed-statefulset.yaml @@ -0,0 +1,49 @@ +apiVersion: apps/v1beta1 +kind: StatefulSet +metadata: + name: minio +spec: + serviceName: minio + replicas: 4 + template: + metadata: + annotations: + pod.alpha.kubernetes.io/initialized: "true" + labels: + app: minio + spec: + containers: + - name: minio + env: + - name: MINIO_ACCESS_KEY + value: "minio" + - name: MINIO_SECRET_KEY + value: "minio123" + image: minio/minio:RELEASE.2017-05-05T01-14-51Z + args: + - server + - http://minio-0.minio.default.svc.cluster.local/data + - http://minio-1.minio.default.svc.cluster.local/data + - http://minio-2.minio.default.svc.cluster.local/data + - http://minio-3.minio.default.svc.cluster.local/data + ports: + - containerPort: 9000 + hostPort: 9000 + # These volume mounts are persistent. Each pod in the PetSet + # gets a volume mounted based on this field. + volumeMounts: + - name: data + mountPath: /data + # These are converted to volume claims by the controller + # and mounted at the paths mentioned above. + volumeClaimTemplates: + - metadata: + name: data + annotations: + volume.alpha.kubernetes.io/storage-class: anything + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 10Gi diff --git a/docs/orchestration/kubernetes-yaml/minio-standalone-deployment.yaml b/docs/orchestration/kubernetes-yaml/minio-standalone-deployment.yaml new file mode 100644 index 000000000..51218e4c5 --- /dev/null +++ b/docs/orchestration/kubernetes-yaml/minio-standalone-deployment.yaml @@ -0,0 +1,40 @@ +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + # This name uniquely identifies the Deployment + name: minio-deployment +spec: + strategy: + type: Recreate + template: + metadata: + labels: + # Label is used as selector in the service. + app: minio + spec: + # Refer to the PVC created earlier + volumes: + - name: storage + persistentVolumeClaim: + # Name of the PVC created earlier + claimName: minio-pv-claim + containers: + - name: minio + # Pulls the default Minio image from Docker Hub + image: minio/minio:RELEASE.2017-05-05T01-14-51Z + args: + - server + - /storage + env: + # Minio access key and secret key + - name: MINIO_ACCESS_KEY + value: "minio" + - name: MINIO_SECRET_KEY + value: "minio123" + ports: + - containerPort: 9000 + hostPort: 9000 + # Mount the volume into the pod + volumeMounts: + - name: storage # must match the volume name, above + mountPath: "/storage" diff --git a/docs/orchestration/kubernetes-yaml/minio-standalone-pvc.yaml b/docs/orchestration/kubernetes-yaml/minio-standalone-pvc.yaml new file mode 100644 index 000000000..edd05215a --- /dev/null +++ b/docs/orchestration/kubernetes-yaml/minio-standalone-pvc.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + # This name uniquely identifies the PVC. Will be used in deployment below. + name: minio-pv-claim + annotations: + volume.alpha.kubernetes.io/storage-class: anything + labels: + app: minio-storage-claim +spec: + # Read more about access modes here: http://kubernetes.io/docs/user-guide/persistent-volumes/#access-modes + accessModes: + - ReadWriteOnce + resources: + # This is the request for storage. Should be available in the cluster. + requests: + storage: 10Gi diff --git a/docs/orchestration/kubernetes-yaml/minio-standalone-service.yaml b/docs/orchestration/kubernetes-yaml/minio-standalone-service.yaml new file mode 100644 index 000000000..60514a863 --- /dev/null +++ b/docs/orchestration/kubernetes-yaml/minio-standalone-service.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Service +metadata: + name: minio-service +spec: + type: LoadBalancer + ports: + - port: 9000 + targetPort: 9000 + protocol: TCP + selector: + app: minio diff --git a/docs/orchestration/kubernetes/README.md b/docs/orchestration/kubernetes/README.md index b4f3d91fd..0413cd2d5 100644 --- a/docs/orchestration/kubernetes/README.md +++ b/docs/orchestration/kubernetes/README.md @@ -4,7 +4,7 @@ Kubernetes concepts like Deployments and StatefulSets provide perfect platform t - Minio [Helm](https://helm.sh) Chart offers a customizable and easy Minio deployment, with a single command. Read more about Minio Helm deployment [here](#prerequisites). -- You can also explore Kubernetes [Minio example](https://github.com/kubernetes/kubernetes/blob/master/examples/storage/minio/README.md) to deploy Minio using `.yaml` files. +- You can also explore Kubernetes [Minio example](https://github.com/minio/minio/blob/master/docs/orchestration/kubernetes-yaml/README.md) to deploy Minio using `.yaml` files. - If you'd like to get started with Minio on Kubernetes without having to create a real container cluster, you can also [deploy Minio locally](https://raw.githubusercontent.com/minio/minio/master/docs/orchestration/minikube/README.md) with MiniKube. From 01e9adc4b3b38571d54d2a61fd002d5d69a83835 Mon Sep 17 00:00:00 2001 From: Remco Verhoef Date: Thu, 4 May 2017 20:03:56 -0700 Subject: [PATCH 02/80] Implement anonymous uploads, fixes #4250 (#4259) --- cmd/gateway-azure-anonymous.go | 7 ++ cmd/gateway-handlers.go | 129 +++++++++++++++++++++++++++++++++ cmd/gateway-router.go | 3 + cmd/gateway-s3-anonymous.go | 31 ++++++++ 4 files changed, 170 insertions(+) diff --git a/cmd/gateway-azure-anonymous.go b/cmd/gateway-azure-anonymous.go index 820905157..69dcc1aa5 100644 --- a/cmd/gateway-azure-anonymous.go +++ b/cmd/gateway-azure-anonymous.go @@ -57,6 +57,13 @@ func (a AzureObjects) AnonGetBucketInfo(bucket string) (bucketInfo BucketInfo, e return bucketInfo, nil } +// AnonPutObject - SendPUT request without authentication. +// This is needed when clients send PUT requests on objects that can be uploaded without auth. +func (a AzureObjects) AnonPutObject(bucket, object string, size int64, data io.Reader, metadata map[string]string, sha256sum string) (objInfo ObjectInfo, err error) { + // azure doesn't support anonymous put + return ObjectInfo{}, traceError(NotImplemented{}) +} + // 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) { diff --git a/cmd/gateway-handlers.go b/cmd/gateway-handlers.go index 28ed69e23..269a3e31c 100644 --- a/cmd/gateway-handlers.go +++ b/cmd/gateway-handlers.go @@ -20,7 +20,9 @@ import ( "io" "io/ioutil" "net/http" + "strconv" + "encoding/hex" "encoding/json" "encoding/xml" @@ -151,6 +153,133 @@ func (api gatewayAPIHandlers) GetObjectHandler(w http.ResponseWriter, r *http.Re } } +// PutObjectHandler - PUT Object +// ---------- +// This implementation of the PUT operation adds an object to a bucket. +func (api gatewayAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Request) { + objectAPI := api.ObjectAPI() + if objectAPI == nil { + writeErrorResponse(w, ErrServerNotInitialized, r.URL) + 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 + } + + var object, bucket string + vars := router.Vars(r) + bucket = vars["bucket"] + object = vars["object"] + + // Get Content-Md5 sent by client and verify if valid + md5Bytes, err := checkValidMD5(r.Header.Get("Content-Md5")) + if err != nil { + errorIf(err, "Unable to validate content-md5 format.") + writeErrorResponse(w, ErrInvalidDigest, r.URL) + return + } + + /// if Content-Length is unknown/missing, deny the request + size := r.ContentLength + reqAuthType := getRequestAuthType(r) + if reqAuthType == authTypeStreamingSigned { + sizeStr := r.Header.Get("x-amz-decoded-content-length") + size, err = strconv.ParseInt(sizeStr, 10, 64) + if err != nil { + errorIf(err, "Unable to parse `x-amz-decoded-content-length` into its integer value", sizeStr) + writeErrorResponse(w, toAPIErrorCode(err), r.URL) + return + } + } + if size == -1 { + writeErrorResponse(w, ErrMissingContentLength, r.URL) + return + } + + /// maximum Upload size for objects in a single operation + if isMaxObjectSize(size) { + writeErrorResponse(w, ErrEntityTooLarge, r.URL) + return + } + + // Extract metadata to be saved from incoming HTTP header. + metadata := extractMetadataFromHeader(r.Header) + if reqAuthType == authTypeStreamingSigned { + if contentEncoding, ok := metadata["content-encoding"]; ok { + contentEncoding = trimAwsChunkedContentEncoding(contentEncoding) + if contentEncoding != "" { + // Make sure to trim and save the content-encoding + // parameter for a streaming signature which is set + // to a custom value for example: "aws-chunked,gzip". + metadata["content-encoding"] = contentEncoding + } else { + // Trimmed content encoding is empty when the header + // value is set to "aws-chunked" only. + + // Make sure to delete the content-encoding parameter + // for a streaming signature which is set to value + // for example: "aws-chunked" + delete(metadata, "content-encoding") + } + } + } + + // Make sure we hex encode md5sum here. + metadata["md5Sum"] = hex.EncodeToString(md5Bytes) + + sha256sum := "" + + // Lock the object. + objectLock := globalNSMutex.NewNSLock(bucket, object) + objectLock.Lock() + defer objectLock.Unlock() + + var objInfo ObjectInfo + switch reqAuthType { + default: + // For all unknown auth types return error. + writeErrorResponse(w, ErrAccessDenied, r.URL) + return + case authTypeAnonymous: + // Create anonymous object. + objInfo, err = objectAPI.AnonPutObject(bucket, object, size, r.Body, metadata, sha256sum) + case authTypeStreamingSigned: + // Initialize stream signature verifier. + reader, s3Error := newSignV4ChunkedReader(r) + if s3Error != ErrNone { + errorIf(errSignatureMismatch, dumpRequest(r)) + writeErrorResponse(w, s3Error, r.URL) + return + } + objInfo, err = objectAPI.PutObject(bucket, object, size, reader, metadata, sha256sum) + case authTypeSignedV2, authTypePresignedV2: + s3Error := isReqAuthenticatedV2(r) + if s3Error != ErrNone { + errorIf(errSignatureMismatch, dumpRequest(r)) + writeErrorResponse(w, s3Error, r.URL) + return + } + objInfo, err = objectAPI.PutObject(bucket, object, size, r.Body, metadata, sha256sum) + case authTypePresigned, authTypeSigned: + if s3Error := reqSignatureV4Verify(r, serverConfig.GetRegion()); s3Error != ErrNone { + errorIf(errSignatureMismatch, dumpRequest(r)) + writeErrorResponse(w, s3Error, r.URL) + return + } + if !skipContentSha256Cksum(r) { + sha256sum = r.Header.Get("X-Amz-Content-Sha256") + } + // Create object. + objInfo, err = objectAPI.PutObject(bucket, object, size, r.Body, metadata, sha256sum) + } + + w.Header().Set("ETag", "\""+objInfo.MD5Sum+"\"") + writeSuccessResponseHeadersOnly(w) +} + // HeadObjectHandler - HEAD Object // ----------- // The HEAD operation retrieves metadata from an object without returning the object itself. diff --git a/cmd/gateway-router.go b/cmd/gateway-router.go index 1cd3589b1..b1d16f310 100644 --- a/cmd/gateway-router.go +++ b/cmd/gateway-router.go @@ -31,6 +31,9 @@ type GatewayLayer interface { AnonGetObject(bucket, object string, startOffset int64, length int64, writer io.Writer) (err error) AnonGetObjectInfo(bucket, object string) (objInfo ObjectInfo, err error) + + AnonPutObject(bucket string, object string, size int64, data io.Reader, metadata map[string]string, sha256sum string) (ObjectInfo, error) + SetBucketPolicies(string, policy.BucketAccessPolicy) error GetBucketPolicies(string) (policy.BucketAccessPolicy, error) DeleteBucketPolicies(string) error diff --git a/cmd/gateway-s3-anonymous.go b/cmd/gateway-s3-anonymous.go index bcaf6036f..d4fc4e31a 100644 --- a/cmd/gateway-s3-anonymous.go +++ b/cmd/gateway-s3-anonymous.go @@ -17,11 +17,42 @@ package cmd import ( + "encoding/hex" "io" minio "github.com/minio/minio-go" ) +// AnonPutObject creates a new object anonymously with the incoming data, +func (l *s3Gateway) AnonPutObject(bucket string, object string, size int64, data io.Reader, metadata map[string]string, sha256sum string) (ObjectInfo, error) { + var sha256sumBytes []byte + + var err error + if sha256sum != "" { + sha256sumBytes, err = hex.DecodeString(sha256sum) + if err != nil { + return ObjectInfo{}, s3ToObjectError(traceError(err), bucket, object) + } + } + + var md5sumBytes []byte + md5sum := metadata["md5Sum"] + if md5sum != "" { + md5sumBytes, err = hex.DecodeString(md5sum) + if err != nil { + return ObjectInfo{}, s3ToObjectError(traceError(err), bucket, object) + } + delete(metadata, "md5Sum") + } + + oi, err := l.anonClient.PutObject(bucket, object, size, data, md5sumBytes, sha256sumBytes, toMinioClientMetadata(metadata)) + if err != nil { + return ObjectInfo{}, s3ToObjectError(traceError(err), bucket, object) + } + + return fromMinioClientObjectInfo(bucket, oi), nil +} + // AnonGetObject - Get object anonymously func (l *s3Gateway) AnonGetObject(bucket string, key string, startOffset int64, length int64, writer io.Writer) error { r := minio.NewGetReqHeaders() From 99ddd35343a74f2f1a625e644a4bdb6850653da2 Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Fri, 5 May 2017 08:28:08 -0700 Subject: [PATCH 03/80] docs: use IEC format such as iB everywhere. (#4247) --- cmd/object-api-multipart_test.go | 4 ++-- cmd/object-handlers_test.go | 22 +++++++++++----------- docs/minio-limitations.md | 2 +- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/cmd/object-api-multipart_test.go b/cmd/object-api-multipart_test.go index 5ac80fcf6..b78bb8c7d 100644 --- a/cmd/object-api-multipart_test.go +++ b/cmd/object-api-multipart_test.go @@ -1782,8 +1782,8 @@ func testObjectCompleteMultipartUpload(obj ObjectLayer, instanceType string, t T } uploadIDs = append(uploadIDs, uploadID) - // Parts with size greater than 5 MB. - // Generating a 6MB byte array. + // Parts with size greater than 5 MiB. + // Generating a 6MiB byte array. validPart := bytes.Repeat([]byte("abcdef"), 1*humanize.MiByte) validPartMD5 := getMD5Hash(validPart) // Create multipart parts. diff --git a/cmd/object-handlers_test.go b/cmd/object-handlers_test.go index ddc6d9091..4c48f303e 100644 --- a/cmd/object-handlers_test.go +++ b/cmd/object-handlers_test.go @@ -2126,8 +2126,8 @@ func testAPICompleteMultipartHandler(obj ObjectLayer, instanceType, bucketName s uploadIDs = append(uploadIDs, uploadID) } - // Parts with size greater than 5 MB. - // Generating a 6MB byte array. + // Parts with size greater than 5 MiB. + // Generating a 6 MiB byte array. validPart := bytes.Repeat([]byte("abcdef"), 1*humanize.MiByte) validPartMD5 := getMD5Hash(validPart) // Create multipart parts. @@ -2147,11 +2147,11 @@ func testAPICompleteMultipartHandler(obj ObjectLayer, instanceType, bucketName s {bucketName, objectName, uploadIDs[0], 2, "efgh", "1f7690ebdd9b4caf8fab49ca1757bf27", int64(len("efgh"))}, {bucketName, objectName, uploadIDs[0], 3, "ijkl", "09a0877d04abf8759f99adec02baf579", int64(len("abcd"))}, {bucketName, objectName, uploadIDs[0], 4, "mnop", "e132e96a5ddad6da8b07bba6f6131fef", int64(len("abcd"))}, - // Part with size larger than 5Mb. + // Part with size larger than 5 MiB. {bucketName, objectName, uploadIDs[0], 5, string(validPart), validPartMD5, int64(len(string(validPart)))}, {bucketName, objectName, uploadIDs[0], 6, string(validPart), validPartMD5, int64(len(string(validPart)))}, - // Part with size larger than 5Mb. + // Part with size larger than 5 MiB. // Parts uploaded for anonymous/unsigned API handler test. {bucketName, objectName, uploadIDs[1], 1, string(validPart), validPartMD5, int64(len(string(validPart)))}, {bucketName, objectName, uploadIDs[1], 2, string(validPart), validPartMD5, int64(len(string(validPart)))}, @@ -2192,7 +2192,7 @@ func testAPICompleteMultipartHandler(obj ObjectLayer, instanceType, bucketName s }, // inputParts - 3. // Case with valid parts,but parts are unsorted. - // Part size greater than 5MB. + // Part size greater than 5 MiB. { []completePart{ {ETag: validPartMD5, PartNumber: 6}, @@ -2201,7 +2201,7 @@ func testAPICompleteMultipartHandler(obj ObjectLayer, instanceType, bucketName s }, // inputParts - 4. // Case with valid part. - // Part size greater than 5MB. + // Part size greater than 5 MiB. { []completePart{ {ETag: validPartMD5, PartNumber: 5}, @@ -2211,7 +2211,7 @@ func testAPICompleteMultipartHandler(obj ObjectLayer, instanceType, bucketName s // inputParts - 5. // Used for the case of testing for anonymous API request. - // Part size greater than 5MB. + // Part size greater than 5 MiB. { []completePart{ {ETag: validPartMD5, PartNumber: 1}, @@ -2481,8 +2481,8 @@ func testAPIAbortMultipartHandler(obj ObjectLayer, instanceType, bucketName stri uploadIDs = append(uploadIDs, uploadID) } - // Parts with size greater than 5 MB. - // Generating a 6MB byte array. + // Parts with size greater than 5 MiB. + // Generating a 6 MiB byte array. validPart := bytes.Repeat([]byte("abcdef"), 1*humanize.MiByte) validPartMD5 := getMD5Hash(validPart) // Create multipart parts. @@ -2502,11 +2502,11 @@ func testAPIAbortMultipartHandler(obj ObjectLayer, instanceType, bucketName stri {bucketName, objectName, uploadIDs[0], 2, "efgh", "1f7690ebdd9b4caf8fab49ca1757bf27", int64(len("efgh"))}, {bucketName, objectName, uploadIDs[0], 3, "ijkl", "09a0877d04abf8759f99adec02baf579", int64(len("abcd"))}, {bucketName, objectName, uploadIDs[0], 4, "mnop", "e132e96a5ddad6da8b07bba6f6131fef", int64(len("abcd"))}, - // Part with size larger than 5Mb. + // Part with size larger than 5 MiB. {bucketName, objectName, uploadIDs[0], 5, string(validPart), validPartMD5, int64(len(string(validPart)))}, {bucketName, objectName, uploadIDs[0], 6, string(validPart), validPartMD5, int64(len(string(validPart)))}, - // Part with size larger than 5Mb. + // Part with size larger than 5 MiB. // Parts uploaded for anonymous/unsigned API handler test. {bucketName, objectName, uploadIDs[1], 1, string(validPart), validPartMD5, int64(len(string(validPart)))}, {bucketName, objectName, uploadIDs[1], 2, string(validPart), validPartMD5, int64(len(string(validPart)))}, diff --git a/docs/minio-limitations.md b/docs/minio-limitations.md index 9e59ee747..9ce71969a 100644 --- a/docs/minio-limitations.md +++ b/docs/minio-limitations.md @@ -13,7 +13,7 @@ |Item|Specification| |:---|:---| -|Web browser upload size limit| 5GB| +|Web browser upload size limit| 5GiB| ### Limits of S3 API From 76f4f20609ae1c92a5e6bb75fc4a5e8ddac9e9ff Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Fri, 5 May 2017 08:49:09 -0700 Subject: [PATCH 04/80] fs: Migrate object metadata to objects directory. (#4195) Fixes #3352 --- cmd/format-config-v1.go | 41 ++++ cmd/fs-v1-helpers.go | 43 +++- cmd/fs-v1-helpers_test.go | 43 +++- cmd/fs-v1-metadata.go | 187 ++++++++++++++--- cmd/fs-v1-metadata_test.go | 4 +- cmd/fs-v1-multipart.go | 2 +- cmd/fs-v1.go | 155 +++++++++++--- cmd/fs-v1_test.go | 380 +++++++++++++++++++++++++++++++++- cmd/object-api-common.go | 3 + cmd/posix-list-dir-others.go | 2 + cmd/posix.go | 2 +- cmd/service.go | 2 +- docs/shared-backend/DESIGN.md | 4 +- 13 files changed, 775 insertions(+), 93 deletions(-) diff --git a/cmd/format-config-v1.go b/cmd/format-config-v1.go index c6c5d687e..4ea2bb24e 100644 --- a/cmd/format-config-v1.go +++ b/cmd/format-config-v1.go @@ -20,8 +20,12 @@ import ( "encoding/json" "errors" "fmt" + "io" + "io/ioutil" "reflect" "sync" + + "github.com/minio/minio/pkg/lock" ) // fsFormat - structure holding 'fs' format. @@ -47,6 +51,43 @@ type formatConfigV1 struct { XL *xlFormat `json:"xl,omitempty"` // XL field holds xl format. } +func (f *formatConfigV1) WriteTo(lk *lock.LockedFile) (n int64, err error) { + // Serialize to prepare to write to disk. + var fbytes []byte + fbytes, err = json.Marshal(f) + if err != nil { + return 0, traceError(err) + } + if err = lk.Truncate(0); err != nil { + return 0, traceError(err) + } + _, err = lk.Write(fbytes) + if err != nil { + return 0, traceError(err) + } + return int64(len(fbytes)), nil +} + +func (f *formatConfigV1) ReadFrom(lk *lock.LockedFile) (n int64, err error) { + var fbytes []byte + fi, err := lk.Stat() + if err != nil { + return 0, traceError(err) + } + fbytes, err = ioutil.ReadAll(io.NewSectionReader(lk, 0, fi.Size())) + if err != nil { + return 0, traceError(err) + } + if len(fbytes) == 0 { + return 0, traceError(io.EOF) + } + // Decode `format.json`. + if err = json.Unmarshal(fbytes, f); err != nil { + return 0, traceError(err) + } + return int64(len(fbytes)), nil +} + /* All disks online diff --git a/cmd/fs-v1-helpers.go b/cmd/fs-v1-helpers.go index dd71679fc..13dc04616 100644 --- a/cmd/fs-v1-helpers.go +++ b/cmd/fs-v1-helpers.go @@ -230,7 +230,7 @@ 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(filePath string, reader io.Reader, buf []byte, fallocSize int64) (int64, error) { - if filePath == "" || reader == nil || buf == nil { + if filePath == "" || reader == nil { return 0, traceError(errInvalidArgument) } @@ -263,11 +263,18 @@ func fsCreateFile(filePath string, reader io.Reader, buf []byte, fallocSize int6 } } - bytesWritten, err := io.CopyBuffer(writer, reader, buf) - if err != nil { - return 0, traceError(err) + var bytesWritten int64 + if buf != nil { + bytesWritten, err = io.CopyBuffer(writer, reader, buf) + if err != nil { + return 0, traceError(err) + } + } else { + bytesWritten, err = io.Copy(writer, reader) + if err != nil { + return 0, traceError(err) + } } - return bytesWritten, nil } @@ -276,6 +283,12 @@ func fsRemoveUploadIDPath(basePath, uploadIDPath string) error { if basePath == "" || uploadIDPath == "" { return traceError(errInvalidArgument) } + if err := checkPathLength(basePath); err != nil { + return traceError(err) + } + if err := checkPathLength(uploadIDPath); err != nil { + return traceError(err) + } // List all the entries in uploadID. entries, err := readDir(uploadIDPath) @@ -319,6 +332,26 @@ func fsFAllocate(fd int, offset int64, len int64) (err error) { // Renames source path to destination path, creates all the // missing parents if they don't exist. func fsRenameFile(sourcePath, destPath string) error { + if err := checkPathLength(sourcePath); err != nil { + return traceError(err) + } + if err := checkPathLength(destPath); err != nil { + return traceError(err) + } + // Verify if source path exists. + if _, err := os.Stat(preparePath(sourcePath)); err != nil { + if os.IsNotExist(err) { + return traceError(errFileNotFound) + } else if os.IsPermission(err) { + return traceError(errFileAccessDenied) + } else if isSysErrPathNotFound(err) { + return traceError(errFileNotFound) + } else if isSysErrNotDir(err) { + // File path cannot be verified since one of the parents is a file. + return traceError(errFileAccessDenied) + } + return traceError(err) + } if err := mkdirAll(pathutil.Dir(destPath), 0777); err != nil { return traceError(err) } diff --git a/cmd/fs-v1-helpers_test.go b/cmd/fs-v1-helpers_test.go index b32b6b169..49003f686 100644 --- a/cmd/fs-v1-helpers_test.go +++ b/cmd/fs-v1-helpers_test.go @@ -26,6 +26,31 @@ import ( "github.com/minio/minio/pkg/lock" ) +func TestFSRenameFile(t *testing.T) { + // create posix test setup + _, path, err := newPosixTestSetup() + if err != nil { + t.Fatalf("Unable to create posix test setup, %s", err) + } + defer removeAll(path) + + if err = fsMkdir(pathJoin(path, "testvolume1")); err != nil { + t.Fatal(err) + } + if err = fsRenameFile(pathJoin(path, "testvolume1"), pathJoin(path, "testvolume2")); err != nil { + t.Fatal(err) + } + if err = fsRenameFile(pathJoin(path, "testvolume1"), pathJoin(path, "testvolume2")); errorCause(err) != errFileNotFound { + t.Fatal(err) + } + if err = fsRenameFile(pathJoin(path, "my-obj-del-0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001"), pathJoin(path, "testvolume2")); errorCause(err) != errFileNameTooLong { + t.Fatal("Unexpected error", err) + } + if err = fsRenameFile(pathJoin(path, "testvolume1"), pathJoin(path, "my-obj-del-0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001")); errorCause(err) != errFileNameTooLong { + t.Fatal("Unexpected error", err) + } +} + func TestFSStats(t *testing.T) { // create posix test setup _, path, err := newPosixTestSetup() @@ -48,9 +73,8 @@ func TestFSStats(t *testing.T) { t.Fatalf("Unable to create volume, %s", err) } - var buf = make([]byte, 4096) var reader = bytes.NewReader([]byte("Hello, world")) - if _, err = fsCreateFile(pathJoin(path, "success-vol", "success-file"), reader, buf, reader.Size()); err != nil { + if _, err = fsCreateFile(pathJoin(path, "success-vol", "success-file"), reader, nil, 0); err != nil { t.Fatalf("Unable to create file, %s", err) } // Seek back. @@ -60,7 +84,7 @@ func TestFSStats(t *testing.T) { t.Fatal("Unexpected error", err) } - if _, err = fsCreateFile(pathJoin(path, "success-vol", "path/to/success-file"), reader, buf, reader.Size()); err != nil { + if _, err = fsCreateFile(pathJoin(path, "success-vol", "path/to/success-file"), reader, nil, 0); err != nil { t.Fatalf("Unable to create file, %s", err) } // Seek back. @@ -174,9 +198,8 @@ func TestFSCreateAndOpen(t *testing.T) { t.Fatal("Unexpected error", err) } - var buf = make([]byte, 4096) var reader = bytes.NewReader([]byte("Hello, world")) - if _, err = fsCreateFile(pathJoin(path, "success-vol", "success-file"), reader, buf, reader.Size()); err != nil { + if _, err = fsCreateFile(pathJoin(path, "success-vol", "success-file"), reader, nil, 0); err != nil { t.Fatalf("Unable to create file, %s", err) } // Seek back. @@ -204,7 +227,7 @@ func TestFSCreateAndOpen(t *testing.T) { } for i, testCase := range testCases { - _, err = fsCreateFile(pathJoin(path, testCase.srcVol, testCase.srcPath), reader, buf, reader.Size()) + _, err = fsCreateFile(pathJoin(path, testCase.srcVol, testCase.srcPath), reader, nil, 0) if errorCause(err) != testCase.expectedErr { t.Errorf("Test case %d: Expected: \"%s\", got: \"%s\"", i+1, testCase.expectedErr, err) } @@ -297,15 +320,14 @@ func TestFSRemoves(t *testing.T) { t.Fatalf("Unable to create directory, %s", err) } - var buf = make([]byte, 4096) var reader = bytes.NewReader([]byte("Hello, world")) - if _, err = fsCreateFile(pathJoin(path, "success-vol", "success-file"), reader, buf, reader.Size()); err != nil { + if _, err = fsCreateFile(pathJoin(path, "success-vol", "success-file"), reader, nil, 0); err != nil { t.Fatalf("Unable to create file, %s", err) } // Seek back. reader.Seek(0, 0) - if _, err = fsCreateFile(pathJoin(path, "success-vol", "success-file-new"), reader, buf, reader.Size()); err != nil { + if _, err = fsCreateFile(pathJoin(path, "success-vol", "success-file-new"), reader, nil, 0); err != nil { t.Fatalf("Unable to create file, %s", err) } // Seek back. @@ -417,9 +439,8 @@ func TestFSRemoveMeta(t *testing.T) { filePath := pathJoin(fsPath, "success-vol", "success-file") - var buf = make([]byte, 4096) var reader = bytes.NewReader([]byte("Hello, world")) - if _, err = fsCreateFile(filePath, reader, buf, reader.Size()); err != nil { + if _, err = fsCreateFile(filePath, reader, nil, 0); err != nil { t.Fatalf("Unable to create file, %s", err) } diff --git a/cmd/fs-v1-metadata.go b/cmd/fs-v1-metadata.go index 260cf13b5..4df6a8516 100644 --- a/cmd/fs-v1-metadata.go +++ b/cmd/fs-v1-metadata.go @@ -18,6 +18,8 @@ package cmd import ( "encoding/json" + "errors" + "fmt" "io" "io/ioutil" "os" @@ -25,6 +27,7 @@ import ( "sort" "strings" + "github.com/minio/minio-go/pkg/set" "github.com/minio/minio/pkg/lock" "github.com/minio/minio/pkg/mimedb" "github.com/tidwall/gjson" @@ -225,9 +228,20 @@ const ( // FS backend meta format. fsMetaFormat = "fs" + // FS backend format version. + fsFormatVersion = fsFormatV2 + // Add more constants here. ) +// FS format version strings. +const ( + fsFormatV1 = "1" // Previous format. + fsFormatV2 = "2" // Current format. + // Proceed to add "3" when we + // change the backend format in future. +) + // newFSMetaV1 - initializes new fsMetaV1. func newFSMetaV1() (fsMeta fsMetaV1) { fsMeta = fsMetaV1{} @@ -237,58 +251,167 @@ func newFSMetaV1() (fsMeta fsMetaV1) { return fsMeta } -// newFSFormatV1 - initializes new formatConfigV1 with FS format info. -func newFSFormatV1() (format *formatConfigV1) { +// newFSFormatV2 - initializes new formatConfigV1 with FS format version 2. +func newFSFormatV2() (format *formatConfigV1) { return &formatConfigV1{ Version: "1", Format: "fs", FS: &fsFormat{ - Version: "1", + Version: fsFormatV2, }, } } -// loads format.json from minioMetaBucket if it exists. -func loadFormatFS(fsPath string) (*formatConfigV1, error) { - rlk, err := lock.RLockedOpenFile(pathJoin(fsPath, minioMetaBucket, fsFormatJSONFile)) - if err != nil { - if os.IsNotExist(err) { - return nil, errUnformattedDisk +// Checks if input format is version 1 and 2. +func isFSValidFormat(formatCfg *formatConfigV1) bool { + // Supported format versions. + var supportedFormatVersions = []string{ + fsFormatV1, + fsFormatV2, + // New supported versions here. + } + + // Check for supported format versions. + for _, version := range supportedFormatVersions { + if formatCfg.FS.Version == version { + return true } - return nil, err } - defer rlk.Close() - - formatBytes, err := ioutil.ReadAll(rlk) - if err != nil { - return nil, err - } - - format := &formatConfigV1{} - if err = json.Unmarshal(formatBytes, format); err != nil { - return nil, err - } - - return format, nil + return false } -// writes FS format (format.json) into minioMetaBucket. -func saveFormatFS(formatPath string, fsFormat *formatConfigV1) error { - metadataBytes, err := json.Marshal(fsFormat) - if err != nil { +// errFSFormatOld- old fs format. +var errFSFormatOld = errors.New("old FS format found") + +// Checks if the loaded `format.json` is valid and +// is expected to be of the requested version. +func checkFormatFS(format *formatConfigV1, formatVersion string) error { + if format == nil { + return errUnexpected + } + + // Validate if we have the same format. + if format.Format != "fs" { + return fmt.Errorf("Unable to recognize backend format, Disk is not in FS format. %s", format.Format) + } + + // Check if format is currently supported. + if !isFSValidFormat(format) { + return errCorruptedFormat + } + + // Check for format version is current. + if format.FS.Version != formatVersion { + return errFSFormatOld + } + + return nil +} + +// This is just kept as reference, there is no sanity +// check for FS format in version "1". +func checkFormatSanityFSV1(fsPath string) error { + return nil +} + +// Check for sanity of FS format in version "2". +func checkFormatSanityFSV2(fsPath string) error { + buckets, err := readDir(pathJoin(fsPath, minioMetaBucket, bucketConfigPrefix)) + if err != nil && err != errFileNotFound { return err } + // Attempt to validate all the buckets have a sanitized backend. + for _, bucket := range buckets { + entries, rerr := readDir(pathJoin(fsPath, minioMetaBucket, bucketConfigPrefix, bucket)) + if rerr != nil { + return rerr + } + + var expectedConfigs = append(bucketMetadataConfigs, objectMetaPrefix+"/") + entriesSet := set.CreateStringSet(entries...) + expectedConfigsSet := set.CreateStringSet(expectedConfigs...) + + // Entries found shouldn't be more than total + // expected config directories, files. + if len(entriesSet) > len(expectedConfigsSet) { + return errCorruptedFormat + } + + // Look for the difference between entries and the + // expected config set, resulting entries if they + // intersect with original entries set we know + // that the backend has unexpected files. + if !entriesSet.Difference(expectedConfigsSet).IsEmpty() { + return errCorruptedFormat + } + } + return nil +} + +// Check for sanity of FS format for a given version. +func checkFormatSanityFS(fsPath string, fsFormatVersion string) (err error) { + switch fsFormatVersion { + case fsFormatV2: + err = checkFormatSanityFSV2(fsPath) + default: + err = errCorruptedFormat + } + return err +} + +// Initializes a new `format.json` if not present, validates `format.json` +// if already present and migrates to newer version if necessary. Returns +// the final format version. +func initFormatFS(fsPath, fsUUID string) (err error) { + fsFormatPath := pathJoin(fsPath, minioMetaBucket, fsFormatJSONFile) + // fsFormatJSONFile - format.json file stored in minioMetaBucket(.minio.sys) directory. - lk, err := lock.LockedOpenFile(preparePath(formatPath), os.O_CREATE|os.O_WRONLY, 0600) + lk, err := lock.LockedOpenFile(preparePath(fsFormatPath), os.O_RDWR|os.O_CREATE, 0600) if err != nil { - return err + return traceError(err) } defer lk.Close() - _, err = lk.Write(metadataBytes) - // Success. - return err + var format = &formatConfigV1{} + _, err = format.ReadFrom(lk) + // For all unexpected errors, we return. + if err != nil && errorCause(err) != io.EOF { + return traceError(fmt.Errorf("Unable to load 'format.json', %s", err)) + } + + // If we couldn't read anything, The disk is unformatted. + if errorCause(err) == io.EOF { + err = errUnformattedDisk + format = newFSFormatV2() + } else { + // Validate loaded `format.json`. + err = checkFormatFS(format, fsFormatVersion) + if err != nil && err != errFSFormatOld { + return traceError(fmt.Errorf("Unable to validate 'format.json', %s", err)) + } + } + + // Disk is in old format migrate object metadata. + if err == errFSFormatOld { + if merr := migrateFSObject(fsPath, fsUUID); merr != nil { + return merr + } + + // Initialize format v2. + format = newFSFormatV2() + } + + // Rewrite or write format.json depending on if disk + // unformatted and if format is old. + if err == errUnformattedDisk || err == errFSFormatOld { + if _, err = format.WriteTo(lk); err != nil { + return traceError(fmt.Errorf("Unable to initialize 'format.json', %s", err)) + } + } + + // Check for sanity. + return checkFormatSanityFS(fsPath, format.FS.Version) } // Return if the part info in uploadedParts and completeParts are same. diff --git a/cmd/fs-v1-metadata_test.go b/cmd/fs-v1-metadata_test.go index 79e3f35d0..620c43b5c 100644 --- a/cmd/fs-v1-metadata_test.go +++ b/cmd/fs-v1-metadata_test.go @@ -58,7 +58,7 @@ func TestReadFSMetadata(t *testing.T) { } // Construct the full path of fs.json - fsPath := pathJoin("buckets", bucketName, objectName, "fs.json") + fsPath := pathJoin(bucketMetaPrefix, bucketName, objectMetaPrefix, objectName, "fs.json") fsPath = pathJoin(fs.fsPath, minioMetaBucket, fsPath) rlk, err := fs.rwPool.Open(fsPath) @@ -95,7 +95,7 @@ func TestWriteFSMetadata(t *testing.T) { } // Construct the full path of fs.json - fsPath := pathJoin("buckets", bucketName, objectName, "fs.json") + fsPath := pathJoin(bucketMetaPrefix, bucketName, objectMetaPrefix, objectName, "fs.json") fsPath = pathJoin(fs.fsPath, minioMetaBucket, fsPath) rlk, err := fs.rwPool.Open(fsPath) diff --git a/cmd/fs-v1-multipart.go b/cmd/fs-v1-multipart.go index 104f30548..040a7a090 100644 --- a/cmd/fs-v1-multipart.go +++ b/cmd/fs-v1-multipart.go @@ -754,7 +754,7 @@ func (fs fsObjects) CompleteMultipartUpload(bucket string, object string, upload // Wait for any competing PutObject() operation on bucket/object, since same namespace // would be acquired for `fs.json`. - fsMetaPath := pathJoin(fs.fsPath, minioMetaBucket, bucketMetaPrefix, bucket, object, fsMetaJSONFile) + fsMetaPath := pathJoin(fs.fsPath, minioMetaBucket, bucketMetaPrefix, bucket, objectMetaPrefix, object, fsMetaJSONFile) metaFile, err := fs.rwPool.Create(fsMetaPath) if err != nil { fs.rwPool.Close(fsMetaPathMultipart) diff --git a/cmd/fs-v1.go b/cmd/fs-v1.go index 440e7d830..da49092ca 100644 --- a/cmd/fs-v1.go +++ b/cmd/fs-v1.go @@ -24,6 +24,7 @@ import ( "io" "io/ioutil" "os" + "os/signal" "path/filepath" "sort" "syscall" @@ -72,15 +73,117 @@ func initMetaVolumeFS(fsPath, fsUUID string) error { } +// Migrate FS object is a place holder code for all +// FS format migrations. +func migrateFSObject(fsPath, fsUUID string) (err error) { + // Writing message here is important for servers being upgraded. + log.Println("Please do not stop the server.") + + ch := make(chan os.Signal) + defer signal.Stop(ch) + defer close(ch) + + signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM) + go func() { + for { + _, ok := <-ch + if !ok { + break + } + log.Println("Please wait server is being upgraded..") + } + }() + + return migrateFSFormatV1ToV2(fsPath, fsUUID) +} + +// List all buckets at meta bucket prefix in `.minio.sys/buckets/` path. +// This is implemented to avoid a bug on windows with using readDir(). +func fsReaddirMetaBuckets(fsPath string) ([]string, error) { + f, err := os.Open(preparePath(pathJoin(fsPath, minioMetaBucket, bucketConfigPrefix))) + if err != nil { + if os.IsNotExist(err) { + return nil, errFileNotFound + } else if os.IsPermission(err) { + return nil, errFileAccessDenied + } + return nil, err + } + return f.Readdirnames(-1) +} + +var bucketMetadataConfigs = []string{ + bucketNotificationConfig, + bucketListenerConfig, + bucketPolicyConfig, +} + +// Attempts to migrate old object metadata files to newer format +// +// i.e +// ------------------------------------------------------- +// .minio.sys/buckets///fs.json - V1 +// ------------------------------------------------------- +// .minio.sys/buckets//objects//fs.json - V2 +// ------------------------------------------------------- +// +func migrateFSFormatV1ToV2(fsPath, fsUUID string) (err error) { + metaBucket := pathJoin(fsPath, minioMetaBucket, bucketConfigPrefix) + + var buckets []string + buckets, err = fsReaddirMetaBuckets(fsPath) + if err != nil && err != errFileNotFound { + return err + } + + // Migrate all buckets present. + for _, bucket := range buckets { + // Temporary bucket of form .UUID-bucket. + tmpBucket := fmt.Sprintf(".%s-%s", fsUUID, bucket) + + // Rename existing bucket as `.UUID-bucket`. + if err = fsRenameFile(pathJoin(metaBucket, bucket), pathJoin(metaBucket, tmpBucket)); err != nil { + return err + } + + // Create a new bucket name with name as `bucket`. + if err = fsMkdir(pathJoin(metaBucket, bucket)); err != nil { + return err + } + + /// Rename all bucket metadata files to newly created `bucket`. + for _, bucketMetaFile := range bucketMetadataConfigs { + if err = fsRenameFile(pathJoin(metaBucket, tmpBucket, bucketMetaFile), + pathJoin(metaBucket, bucket, bucketMetaFile)); err != nil { + if errorCause(err) != errFileNotFound { + return err + } + } + } + + // Finally rename the temporary bucket to `bucket/objects` directory. + if err = fsRenameFile(pathJoin(metaBucket, tmpBucket), + pathJoin(metaBucket, bucket, objectMetaPrefix)); err != nil { + if errorCause(err) != errFileNotFound { + return err + } + } + } + + log.Printf("Migrating bucket metadata format from \"%s\" to newer format \"%s\"... completed successfully.", fsFormatV1, fsFormatV2) + + // If all goes well we return success. + return nil +} + // newFSObjectLayer - initialize new fs object layer. func newFSObjectLayer(fsPath string) (ObjectLayer, error) { if fsPath == "" { return nil, errInvalidArgument } - var err error // Disallow relative paths, figure out absolute paths. - fsPath, err = filepath.Abs(fsPath) + fsPath, err := filepath.Abs(fsPath) if err != nil { return nil, err } @@ -108,26 +211,6 @@ func newFSObjectLayer(fsPath string) (ObjectLayer, error) { return nil, fmt.Errorf("Unable to initialize '.minio.sys' meta volume, %s", err) } - // Load `format.json`. - format, err := loadFormatFS(fsPath) - if err != nil && err != errUnformattedDisk { - return nil, fmt.Errorf("Unable to load 'format.json', %s", err) - } - - // If the `format.json` doesn't exist create one. - if err == errUnformattedDisk { - fsFormatPath := pathJoin(fsPath, minioMetaBucket, fsFormatJSONFile) - // Initialize format.json, if already exists overwrite it. - if serr := saveFormatFS(fsFormatPath, newFSFormatV1()); serr != nil { - return nil, fmt.Errorf("Unable to initialize 'format.json', %s", serr) - } - } - - // Validate if we have the same format. - if err == nil && format.Format != "fs" { - return nil, fmt.Errorf("Unable to recognize backend format, Disk is not in FS format. %s", format.Format) - } - // Initialize fs objects. fs := &fsObjects{ fsPath: fsPath, @@ -141,6 +224,17 @@ func newFSObjectLayer(fsPath string) (ObjectLayer, error) { }, } + // Initialize `format.json`. + if err = initFormatFS(fsPath, fsUUID); err != nil { + return nil, err + } + + // Once initialized hold read lock for the entire operation + // of filesystem backend. + if _, err = fs.rwPool.Open(pathJoin(fsPath, minioMetaBucket, fsFormatJSONFile)); err != nil { + return nil, err + } + // Initialize and load bucket policies. err = initBucketPolicies(fs) if err != nil { @@ -159,6 +253,9 @@ func newFSObjectLayer(fsPath string) (ObjectLayer, error) { // Should be called when process shuts down. func (fs fsObjects) Shutdown() error { + // Close the format.json read lock. + fs.rwPool.Close(pathJoin(fs.fsPath, minioMetaBucket, fsFormatJSONFile)) + // Cleanup and delete tmp uuid. return fsRemoveAll(pathJoin(fs.fsPath, minioMetaTmpBucket, fs.fsUUID)) } @@ -238,7 +335,7 @@ func (fs fsObjects) ListBuckets() ([]BucketInfo, error) { return nil, traceError(err) } var bucketInfos []BucketInfo - entries, err := readDir(preparePath(fs.fsPath)) + entries, err := readDir(fs.fsPath) if err != nil { return nil, toObjectErr(traceError(errDiskNotFound)) } @@ -322,7 +419,7 @@ func (fs fsObjects) CopyObject(srcBucket, srcObject, dstBucket, dstObject string // Check if this request is only metadata update. cpMetadataOnly := isStringEqual(pathJoin(srcBucket, srcObject), pathJoin(dstBucket, dstObject)) if cpMetadataOnly { - fsMetaPath := pathJoin(fs.fsPath, minioMetaBucket, bucketMetaPrefix, srcBucket, srcObject, fsMetaJSONFile) + fsMetaPath := pathJoin(fs.fsPath, minioMetaBucket, bucketMetaPrefix, srcBucket, objectMetaPrefix, srcObject, fsMetaJSONFile) var wlk *lock.LockedFile wlk, err = fs.rwPool.Write(fsMetaPath) if err != nil { @@ -395,7 +492,7 @@ func (fs fsObjects) GetObject(bucket, object string, offset int64, length int64, } if bucket != minioMetaBucket { - fsMetaPath := pathJoin(fs.fsPath, minioMetaBucket, bucketMetaPrefix, bucket, object, fsMetaJSONFile) + fsMetaPath := pathJoin(fs.fsPath, minioMetaBucket, bucketMetaPrefix, bucket, objectMetaPrefix, object, fsMetaJSONFile) _, err = fs.rwPool.Open(fsMetaPath) if err != nil && err != errFileNotFound { return toObjectErr(traceError(err), bucket, object) @@ -437,7 +534,7 @@ func (fs fsObjects) GetObject(bucket, object string, offset int64, length int64, // getObjectInfo - wrapper for reading object metadata and constructs ObjectInfo. func (fs fsObjects) getObjectInfo(bucket, object string) (ObjectInfo, error) { fsMeta := fsMetaV1{} - fsMetaPath := pathJoin(fs.fsPath, minioMetaBucket, bucketMetaPrefix, bucket, object, fsMetaJSONFile) + fsMetaPath := pathJoin(fs.fsPath, minioMetaBucket, bucketMetaPrefix, bucket, objectMetaPrefix, object, fsMetaJSONFile) // Read `fs.json` to perhaps contend with // parallel Put() operations. @@ -520,7 +617,7 @@ func (fs fsObjects) PutObject(bucket string, object string, size int64, data io. var wlk *lock.LockedFile if bucket != minioMetaBucket { bucketMetaDir := pathJoin(fs.fsPath, minioMetaBucket, bucketMetaPrefix) - fsMetaPath := pathJoin(bucketMetaDir, bucket, object, fsMetaJSONFile) + fsMetaPath := pathJoin(bucketMetaDir, bucket, objectMetaPrefix, object, fsMetaJSONFile) wlk, err = fs.rwPool.Create(fsMetaPath) if err != nil { return ObjectInfo{}, toObjectErr(traceError(err), bucket, object) @@ -647,7 +744,7 @@ func (fs fsObjects) DeleteObject(bucket, object string) error { } minioMetaBucketDir := pathJoin(fs.fsPath, minioMetaBucket) - fsMetaPath := pathJoin(minioMetaBucketDir, bucketMetaPrefix, bucket, object, fsMetaJSONFile) + fsMetaPath := pathJoin(minioMetaBucketDir, bucketMetaPrefix, bucket, objectMetaPrefix, object, fsMetaJSONFile) if bucket != minioMetaBucket { rwlk, lerr := fs.rwPool.Write(fsMetaPath) if lerr == nil { @@ -701,7 +798,7 @@ func (fs fsObjects) listDirFactory(isLeaf isLeafFunc) listDirFunc { // getObjectETag is a helper function, which returns only the md5sum // of the file on the disk. func (fs fsObjects) getObjectETag(bucket, entry string) (string, error) { - fsMetaPath := pathJoin(fs.fsPath, minioMetaBucket, bucketMetaPrefix, bucket, entry, fsMetaJSONFile) + fsMetaPath := pathJoin(fs.fsPath, minioMetaBucket, bucketMetaPrefix, bucket, objectMetaPrefix, entry, fsMetaJSONFile) // Read `fs.json` to perhaps contend with // parallel Put() operations. diff --git a/cmd/fs-v1_test.go b/cmd/fs-v1_test.go index 0cd0290a2..f0b68b111 100644 --- a/cmd/fs-v1_test.go +++ b/cmd/fs-v1_test.go @@ -22,6 +22,8 @@ import ( "os" "path/filepath" "testing" + + "github.com/minio/minio/pkg/lock" ) // TestNewFS - tests initialization of all input disks @@ -85,8 +87,8 @@ func TestFSShutdown(t *testing.T) { } } -// TestFSLoadFormatFS - test loadFormatFS with healty and faulty disks -func TestFSLoadFormatFS(t *testing.T) { +// Tests migrating FS format without .minio.sys/buckets. +func TestFSMigrateObjectWithoutObjects(t *testing.T) { // Prepare for testing disk := filepath.Join(globalTestTmpDir, "minio-"+nextSuffix()) defer removeAll(disk) @@ -100,13 +102,364 @@ func TestFSLoadFormatFS(t *testing.T) { } fsFormatPath := pathJoin(disk, minioMetaBucket, fsFormatJSONFile) - if err := saveFormatFS(preparePath(fsFormatPath), newFSFormatV1()); err != nil { - t.Fatal("Should not fail here", err) + formatCfg := &formatConfigV1{ + Version: "1", + Format: "fs", + FS: &fsFormat{ + Version: "1", + }, } - _, err := loadFormatFS(disk) + + lk, err := lock.LockedOpenFile(preparePath(fsFormatPath), os.O_RDWR|os.O_CREATE, 0600) + if err != nil { + t.Fatal(err) + } + _, err = formatCfg.WriteTo(lk) + lk.Close() if err != nil { t.Fatal("Should not fail here", err) } + + if err = initFormatFS(disk, uuid); err != nil { + t.Fatal("Should not fail with unexpected", err) + } + + formatCfg = &formatConfigV1{} + lk, err = lock.LockedOpenFile(preparePath(fsFormatPath), os.O_RDONLY, 0600) + if err != nil { + t.Fatal(err) + } + _, err = formatCfg.ReadFrom(lk) + lk.Close() + if err != nil { + t.Fatal("Should not fail here", err) + } + if formatCfg.FS.Version != fsFormatV2 { + t.Fatalf("Unexpected version detected expected \"%s\", got %s", fsFormatV2, formatCfg.FS.Version) + } +} + +// Tests migrating FS format without .minio.sys/buckets. +func TestFSMigrateObjectWithErr(t *testing.T) { + // Prepare for testing + disk := filepath.Join(globalTestTmpDir, "minio-"+nextSuffix()) + defer removeAll(disk) + + // Assign a new UUID. + uuid := mustGetUUID() + + // Initialize meta volume, if volume already exists ignores it. + if err := initMetaVolumeFS(disk, uuid); err != nil { + t.Fatal(err) + } + + fsFormatPath := pathJoin(disk, minioMetaBucket, fsFormatJSONFile) + formatCfg := &formatConfigV1{ + Version: "1", + Format: "fs", + FS: &fsFormat{ + Version: "10", + }, + } + + lk, err := lock.LockedOpenFile(preparePath(fsFormatPath), os.O_RDWR|os.O_CREATE, 0600) + if err != nil { + t.Fatal(err) + } + _, err = formatCfg.WriteTo(lk) + lk.Close() + if err != nil { + t.Fatal("Should not fail here", err) + } + + if err = initFormatFS(disk, uuid); err != nil { + if errorCause(err).Error() != + "Unable to validate 'format.json', corrupted backend format" { + t.Fatal("Should not fail with unexpected", err) + } + } + + fsFormatPath = pathJoin(disk, minioMetaBucket, fsFormatJSONFile) + formatCfg = &formatConfigV1{ + Version: "1", + Format: "garbage", + FS: &fsFormat{ + Version: "1", + }, + } + + lk, err = lock.LockedOpenFile(preparePath(fsFormatPath), os.O_RDWR|os.O_CREATE, 0600) + if err != nil { + t.Fatal(err) + } + _, err = formatCfg.WriteTo(lk) + lk.Close() + if err != nil { + t.Fatal("Should not fail here", err) + } + + if err = initFormatFS(disk, uuid); err != nil { + if errorCause(err).Error() != + "Unable to validate 'format.json', Unable to recognize backend format, Disk is not in FS format. garbage" { + t.Fatal("Should not fail with unexpected", err) + } + } + +} + +// Tests migrating FS format with .minio.sys/buckets filled with +// object metadata. +func TestFSMigrateObjectWithObjects(t *testing.T) { + // Prepare for testing + disk := filepath.Join(globalTestTmpDir, "minio-"+nextSuffix()) + defer removeAll(disk) + + // Assign a new UUID. + uuid := mustGetUUID() + + // Initialize meta volume, if volume already exists ignores it. + if err := initMetaVolumeFS(disk, uuid); err != nil { + t.Fatal(err) + } + + fsFormatPath := pathJoin(disk, minioMetaBucket, fsFormatJSONFile) + formatCfg := &formatConfigV1{ + Version: "1", + Format: "fs", + FS: &fsFormat{ + Version: "1", + }, + } + lk, err := lock.LockedOpenFile(preparePath(fsFormatPath), os.O_RDWR|os.O_CREATE, 0600) + if err != nil { + t.Fatal(err) + } + _, err = formatCfg.WriteTo(lk) + lk.Close() + if err != nil { + t.Fatal("Should not fail here", err) + } + + // Construct the full path of fs.json + fsPath1 := pathJoin(bucketMetaPrefix, "testvolume1", "my-object1", fsMetaJSONFile) + fsPath1 = pathJoin(disk, minioMetaBucket, fsPath1) + + fsMetaJSON := `{"version":"1.0.0","format":"fs","minio":{"release":"DEVELOPMENT.2017-03-27T02-26-33Z"},"meta":{"md5Sum":"467886be95c8ecfd71a2900e3f461b4f"}` + if _, err = fsCreateFile(fsPath1, bytes.NewReader([]byte(fsMetaJSON)), nil, 0); err != nil { + t.Fatal(err) + } + + // Construct the full path of fs.json + fsPath2 := pathJoin(bucketMetaPrefix, "testvolume2", "my-object2", fsMetaJSONFile) + fsPath2 = pathJoin(disk, minioMetaBucket, fsPath2) + + fsMetaJSON = `{"version":"1.0.0","format":"fs","minio":{"release":"DEVELOPMENT.2017-03-27T02-26-33Z"},"meta":{"md5Sum":"467886be95c8ecfd71a2900eff461b4d"}` + if _, err = fsCreateFile(fsPath2, bytes.NewReader([]byte(fsMetaJSON)), nil, 0); err != nil { + t.Fatal(err) + } + + // Construct the full path of policy.json + ppath := pathJoin(bucketMetaPrefix, "testvolume2", bucketPolicyConfig) + ppath = pathJoin(disk, minioMetaBucket, ppath) + + policyJSON := `{"Version":"2012-10-17","Statement":[{"Action":["s3:GetBucketLocation","s3:ListBucket"],"Effect":"Allow","Principal":{"AWS":["*"]},"Resource":["arn:aws:s3:::testbucket"],"Sid":""},{"Action":["s3:GetObject"],"Effect":"Allow","Principal":{"AWS":["*"]},"Resource":["arn:aws:s3:::testbucket/*"],"Sid":""}]}` + if _, err = fsCreateFile(ppath, bytes.NewReader([]byte(policyJSON)), nil, 0); err != nil { + t.Fatal(err) + } + + if err = initFormatFS(disk, mustGetUUID()); err != nil { + t.Fatal("Should not fail here", err) + } + + fsPath2 = pathJoin(bucketMetaPrefix, "testvolume2", objectMetaPrefix, "my-object2", fsMetaJSONFile) + fsPath2 = pathJoin(disk, minioMetaBucket, fsPath2) + fi, err := fsStatFile(fsPath2) + if err != nil { + t.Fatal("Path should exist and accessible after migration", err) + } + if fi.IsDir() { + t.Fatalf("Unexpected path %s should be a file", fsPath2) + } + + formatCfg = &formatConfigV1{} + lk, err = lock.LockedOpenFile(preparePath(fsFormatPath), os.O_RDONLY, 0600) + if err != nil { + t.Fatal(err) + } + _, err = formatCfg.ReadFrom(lk) + lk.Close() + if err != nil { + t.Fatal("Should not fail here", err) + } + if formatCfg.FS.Version != fsFormatV2 { + t.Fatalf("Unexpected version detected expected \"%s\", got %s", fsFormatV2, formatCfg.FS.Version) + } + + ppath = pathJoin(bucketMetaPrefix, "testvolume2", "acl.json") + ppath = pathJoin(disk, minioMetaBucket, ppath) + + if _, err = fsCreateFile(ppath, bytes.NewReader([]byte("")), nil, 0); err != nil { + t.Fatal(err) + } + + if err = initFormatFS(disk, mustGetUUID()); errorCause(err) != errCorruptedFormat { + t.Fatal("Should not fail here", err) + } +} + +// TestFSCheckFormatFSErr - test loadFormatFS loading older format. +func TestFSCheckFormatFSErr(t *testing.T) { + // Prepare for testing + disk := filepath.Join(globalTestTmpDir, "minio-"+nextSuffix()) + defer removeAll(disk) + + // Assign a new UUID. + uuid := mustGetUUID() + + // Initialize meta volume, if volume already exists ignores it. + if err := initMetaVolumeFS(disk, uuid); err != nil { + t.Fatal(err) + } + + fsFormatPath := pathJoin(disk, minioMetaBucket, fsFormatJSONFile) + formatCfg := &formatConfigV1{ + Version: "1", + Format: "fs", + FS: &fsFormat{ + Version: "1", + }, + } + + lk, err := lock.LockedOpenFile(preparePath(fsFormatPath), os.O_RDWR|os.O_CREATE, 0600) + if err != nil { + t.Fatal(err) + } + + _, err = formatCfg.WriteTo(lk) + lk.Close() + if err != nil { + t.Fatal(err) + } + + formatCfg = &formatConfigV1{} + lk, err = lock.LockedOpenFile(preparePath(fsFormatPath), os.O_RDWR|os.O_CREATE, 0600) + if err != nil { + t.Fatal(err) + } + _, err = formatCfg.ReadFrom(lk) + lk.Close() + if err != nil { + t.Fatal(err) + } + + if err = checkFormatFS(formatCfg, fsFormatVersion); errorCause(err) != errFSFormatOld { + t.Fatal("Should not fail with unexpected", err) + } + + formatCfg = &formatConfigV1{ + Version: "1", + Format: "fs", + FS: &fsFormat{ + Version: "10", + }, + } + + lk, err = lock.LockedOpenFile(preparePath(fsFormatPath), os.O_RDWR|os.O_CREATE, 0600) + if err != nil { + t.Fatal(err) + } + + _, err = formatCfg.WriteTo(lk) + lk.Close() + if err != nil { + t.Fatal(err) + } + + if err = checkFormatFS(formatCfg, fsFormatVersion); errorCause(err) != errCorruptedFormat { + t.Fatal("Should not fail with unexpected", err) + } + + formatCfg = &formatConfigV1{ + Version: "1", + Format: "garbage", + FS: &fsFormat{ + Version: "1", + }, + } + + lk, err = lock.LockedOpenFile(preparePath(fsFormatPath), os.O_RDWR|os.O_CREATE, 0600) + if err != nil { + t.Fatal(err) + } + + _, err = formatCfg.WriteTo(lk) + lk.Close() + if err != nil { + t.Fatal(err) + } + + if err = checkFormatFS(formatCfg, fsFormatVersion); err != nil { + if errorCause(err).Error() != "Unable to recognize backend format, Disk is not in FS format. garbage" { + t.Fatal("Should not fail with unexpected", err) + } + } + + if err = checkFormatFS(nil, fsFormatVersion); errorCause(err) != errUnexpected { + t.Fatal("Should fail with errUnexpected, but found", err) + } + + formatCfg = &formatConfigV1{ + Version: "1", + Format: "fs", + FS: &fsFormat{ + Version: "2", + }, + } + + lk, err = lock.LockedOpenFile(preparePath(fsFormatPath), os.O_RDWR|os.O_CREATE, 0600) + if err != nil { + t.Fatal(err) + } + + _, err = formatCfg.WriteTo(lk) + lk.Close() + if err != nil { + t.Fatal(err) + } + + // Should not fail. + if err = checkFormatFS(formatCfg, fsFormatVersion); err != nil { + t.Fatal(err) + } +} + +// TestFSCheckFormatFS - test loadFormatFS with healty and faulty disks +func TestFSCheckFormatFS(t *testing.T) { + // Prepare for testing + disk := filepath.Join(globalTestTmpDir, "minio-"+nextSuffix()) + defer removeAll(disk) + + // Assign a new UUID. + uuid := mustGetUUID() + + // Initialize meta volume, if volume already exists ignores it. + if err := initMetaVolumeFS(disk, uuid); err != nil { + t.Fatal(err) + } + + fsFormatPath := pathJoin(disk, minioMetaBucket, fsFormatJSONFile) + lk, err := lock.LockedOpenFile(preparePath(fsFormatPath), os.O_RDWR|os.O_CREATE, 0600) + if err != nil { + t.Fatal(err) + } + + format := newFSFormatV2() + _, err = format.WriteTo(lk) + lk.Close() + if err != nil { + t.Fatal(err) + } + // Loading corrupted format file file, err := os.OpenFile(preparePath(fsFormatPath), os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0666) if err != nil { @@ -114,15 +467,24 @@ func TestFSLoadFormatFS(t *testing.T) { } file.Write([]byte{'b'}) file.Close() - _, err = loadFormatFS(disk) + + lk, err = lock.LockedOpenFile(preparePath(fsFormatPath), os.O_RDWR|os.O_CREATE, 0600) + if err != nil { + t.Fatal(err) + } + + format = &formatConfigV1{} + _, err = format.ReadFrom(lk) + lk.Close() if err == nil { t.Fatal("Should return an error here") } + // Loading format file from disk not found. removeAll(disk) - _, err = loadFormatFS(disk) - if err != nil && err != errUnformattedDisk { - t.Fatal("Should return unformatted disk, but got", err) + _, err = lock.LockedOpenFile(preparePath(fsFormatPath), os.O_RDONLY, 0600) + if err != nil && !os.IsNotExist(err) { + t.Fatal("Should return 'format.json' does not exist, but got", err) } } diff --git a/cmd/object-api-common.go b/cmd/object-api-common.go index c15440362..b2056f01d 100644 --- a/cmd/object-api-common.go +++ b/cmd/object-api-common.go @@ -32,6 +32,9 @@ const ( // Buckets meta prefix. bucketMetaPrefix = "buckets" + // Objects meta prefix. + objectMetaPrefix = "objects" + // Md5Sum of empty string. emptyStrMd5Sum = "d41d8cd98f00b204e9800998ecf8427e" ) diff --git a/cmd/posix-list-dir-others.go b/cmd/posix-list-dir-others.go index 75b53d8ca..2533aa064 100644 --- a/cmd/posix-list-dir-others.go +++ b/cmd/posix-list-dir-others.go @@ -32,6 +32,8 @@ func readDir(dirPath string) (entries []string, err error) { // File is really not found. if os.IsNotExist(err) { return nil, errFileNotFound + } else if os.IsPermission(err) { + return nil, errFileAccessDenied } // File path cannot be verified since one of the parents is a file. diff --git a/cmd/posix.go b/cmd/posix.go index 7e103a983..84fc25c68 100644 --- a/cmd/posix.go +++ b/cmd/posix.go @@ -291,7 +291,7 @@ func (s *posix) ListVols() (volsInfo []VolInfo, err error) { return nil, err } - volsInfo, err = listVols(preparePath(s.diskPath)) + volsInfo, err = listVols(s.diskPath) if err != nil { return nil, err } diff --git a/cmd/service.go b/cmd/service.go index d0af1c0d4..e23a094f2 100644 --- a/cmd/service.go +++ b/cmd/service.go @@ -82,7 +82,7 @@ func (m *ServerMux) handleServiceSignals() error { // Wait for SIGTERM in a go-routine. trapCh := signalTrap(os.Interrupt, syscall.SIGTERM) - go func(<-chan bool) { + go func(trapCh <-chan bool) { <-trapCh globalServiceSignalCh <- serviceStop }(trapCh) diff --git a/docs/shared-backend/DESIGN.md b/docs/shared-backend/DESIGN.md index d22d3520e..86a2aea1f 100644 --- a/docs/shared-backend/DESIGN.md +++ b/docs/shared-backend/DESIGN.md @@ -81,7 +81,7 @@ An example here shows how the contention is handled with GetObject(). GetObject() holds a read lock on `fs.json`. ```go - fsMetaPath := pathJoin(fs.fsPath, minioMetaBucket, bucketMetaPrefix, bucket, object, fsMetaJSONFile) + fsMetaPath := pathJoin(fs.fsPath, minioMetaBucket, bucketMetaPrefix, bucket, objectMetaPrefix, object, fsMetaJSONFile) rlk, err := fs.rwPool.Open(fsMetaPath) if err != nil { return toObjectErr(traceError(err), bucket, object) @@ -98,7 +98,7 @@ GetObject() holds a read lock on `fs.json`. A concurrent PutObject is requested on the same object, PutObject() attempts a write lock on `fs.json`. ```go - fsMetaPath := pathJoin(fs.fsPath, minioMetaBucket, bucketMetaPrefix, bucket, object, fsMetaJSONFile) + fsMetaPath := pathJoin(fs.fsPath, minioMetaBucket, bucketMetaPrefix, bucket, objectMetaPrefix, object, fsMetaJSONFile) wlk, err := fs.rwPool.Create(fsMetaPath) if err != nil { return ObjectInfo{}, toObjectErr(err, bucket, object) From e372b5ed67117c3536362150fc3f852550ba0def Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Fri, 5 May 2017 13:16:58 -0700 Subject: [PATCH 05/80] build: Fix release build names. (#4263) Currently due to the occurrence of 6 arguments from `gen-ldflags.go` leads to a bug where the binaries genenerated have wrong names. As shown below. ``` If you want to build for all, Just press Enter: linux/amd64 --> linux/amd64:github.com/minio/minio $ ls release/linux-amd64/ [2017-05-04 23:08:51 PDT] 17MiB minio [2017-05-04 23:08:51 PDT] 17MiB minio.2017-05-05T06:08:22Z [2017-05-04 23:08:51 PDT] 76B minio.shasum ``` This PR fixes this issue by retaining the previous release binary names. ``` If you want to build for all, Just press Enter: linux/amd64 --> linux/amd64:github.com/minio/minio $ ls release/linux-amd64/ [2017-05-04 23:08:51 PDT] 17MiB minio [2017-05-04 23:08:51 PDT] 17MiB minio.RELEASE.2017-05-05T06-08-22Z [2017-05-04 23:08:51 PDT] 76B minio.shasum ``` --- buildscripts/build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildscripts/build.sh b/buildscripts/build.sh index 79a7778c8..184408c3f 100755 --- a/buildscripts/build.sh +++ b/buildscripts/build.sh @@ -5,7 +5,7 @@ _init() { LDFLAGS=$(go run buildscripts/gen-ldflags.go) # Extract release tag - release_tag=$(echo $LDFLAGS | awk {'print $4'} | cut -f2 -d=) + release_tag=$(echo $LDFLAGS | awk {'print $6'} | cut -f2 -d=) # Verify release tag. if [ -z "$release_tag" ]; then From 2df1e2e9a9c290460a05e7f5ce7727c5bb037a9a Mon Sep 17 00:00:00 2001 From: Anis Elleuch Date: Fri, 5 May 2017 23:20:29 +0100 Subject: [PATCH 06/80] doc: Fix pgsql cmd example (#4265) --- docs/bucket/notifications/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/bucket/notifications/README.md b/docs/bucket/notifications/README.md index aa39cb2e1..d4739273c 100644 --- a/docs/bucket/notifications/README.md +++ b/docs/bucket/notifications/README.md @@ -535,7 +535,7 @@ mc cp myphoto.jpg myminio/images Open PostgreSQL terminal to list the rows in the `bucketevents` table. ``` -$ psql -h 127.0.0.1 -u postgres -p minio_events +$ psql -h 127.0.0.1 -U postgres -d minio_events minio_events=# select * from bucketevents; key | value From 610dbe3479354c10910f1cf17ba9cbae0ea5a55d Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Sat, 6 May 2017 10:16:59 -0700 Subject: [PATCH 07/80] config: Do not migrate config file if not needed. (#4264) Also improve the error message returned by `pkg/quick`. Fixes #4233 --- cmd/config-migrate.go | 176 +++++++++++++++++++++++-------------- cmd/config-migrate_test.go | 40 ++++++++- cmd/server-main.go | 2 +- pkg/quick/encoding.go | 12 ++- pkg/quick/errorutil.go | 25 ++---- pkg/quick/quick.go | 14 ++- pkg/quick/quick_test.go | 36 ++++++++ 7 files changed, 214 insertions(+), 91 deletions(-) diff --git a/cmd/config-migrate.go b/cmd/config-migrate.go index f06f5ddf8..f20c10434 100644 --- a/cmd/config-migrate.go +++ b/cmd/config-migrate.go @@ -27,77 +27,125 @@ import ( // DO NOT EDIT following message template, please open a github issue to discuss instead. var configMigrateMSGTemplate = "Configuration file %s migrated from version '%s' to '%s' successfully.\n" +// Migrates all config versions from "1" to "18". func migrateConfig() error { - // Purge all configs with version '1'. + // Purge all configs with version '1', + // this is a special case since version '1' used + // to be a filename 'fsUsers.json' not 'config.json'. if err := purgeV1(); err != nil { return err } - // Migrate version '2' to '3'. - if err := migrateV2ToV3(); err != nil { - return err - } - // Migrate version '3' to '4'. - if err := migrateV3ToV4(); err != nil { - return err - } - // Migrate version '4' to '5'. - if err := migrateV4ToV5(); err != nil { - return err - } - // Migrate version '5' to '6. - if err := migrateV5ToV6(); err != nil { - return err - } - // Migrate version '6' to '7'. - if err := migrateV6ToV7(); err != nil { - return err - } - // Migrate version '7' to '8'. - if err := migrateV7ToV8(); err != nil { - return err - } - // Migrate version '8' to '9'. - if err := migrateV8ToV9(); err != nil { - return err - } - // Migrate version '9' to '10'. - if err := migrateV9ToV10(); err != nil { - return err - } - // Migrate version '10' to '11'. - if err := migrateV10ToV11(); err != nil { - return err - } - // Migrate version '11' to '12'. - if err := migrateV11ToV12(); err != nil { - return err - } - // Migrate version '12' to '13'. - if err := migrateV12ToV13(); err != nil { - return err - } - // Migrate version '13' to '14'. - if err := migrateV13ToV14(); err != nil { - return err - } - // Migrate version '14' to '15'. - if err := migrateV14ToV15(); err != nil { - return err - } - // Migrate version '15' to '16'. - if err := migrateV15ToV16(); err != nil { - return err - } - // Migrate version '16' to '17'. - if err := migrateV16ToV17(); err != nil { - return err - } - // Migrate version '17' to '18'. - if err := migrateV17ToV18(); err != nil { + + // Load only config version information. + version, err := quick.GetVersion(getConfigFile()) + if err != nil { return err } - return nil + // Conditional to migrate only relevant config versions. + // Upon success migration continues to the next version in sequence. + switch version { + case "2": + // Migrate version '2' to '3'. + if err = migrateV2ToV3(); err != nil { + return err + } + fallthrough + case "3": + // Migrate version '3' to '4'. + if err = migrateV3ToV4(); err != nil { + return err + } + fallthrough + case "4": + // Migrate version '4' to '5'. + if err = migrateV4ToV5(); err != nil { + return err + } + fallthrough + case "5": + // Migrate version '5' to '6. + if err = migrateV5ToV6(); err != nil { + return err + } + fallthrough + case "6": + // Migrate version '6' to '7'. + if err = migrateV6ToV7(); err != nil { + return err + } + fallthrough + case "7": + // Migrate version '7' to '8'. + if err = migrateV7ToV8(); err != nil { + return err + } + fallthrough + case "8": + // Migrate version '8' to '9'. + if err = migrateV8ToV9(); err != nil { + return err + } + fallthrough + case "9": + // Migrate version '9' to '10'. + if err = migrateV9ToV10(); err != nil { + return err + } + fallthrough + case "10": + // Migrate version '10' to '11'. + if err = migrateV10ToV11(); err != nil { + return err + } + fallthrough + case "11": + // Migrate version '11' to '12'. + if err = migrateV11ToV12(); err != nil { + return err + } + fallthrough + case "12": + // Migrate version '12' to '13'. + if err = migrateV12ToV13(); err != nil { + return err + } + fallthrough + case "13": + // Migrate version '13' to '14'. + if err = migrateV13ToV14(); err != nil { + return err + } + fallthrough + case "14": + // Migrate version '14' to '15'. + if err = migrateV14ToV15(); err != nil { + return err + } + fallthrough + case "15": + // Migrate version '15' to '16'. + if err = migrateV15ToV16(); err != nil { + return err + } + fallthrough + case "16": + // Migrate version '16' to '17'. + if err = migrateV16ToV17(); err != nil { + return err + } + fallthrough + case "17": + // Migrate version '17' to '18'. + if err = migrateV17ToV18(); err != nil { + return err + } + fallthrough + case v18: + // No migration needed. this always points to current version. + err = nil + } + return err } // Version '1' is not supported anymore and deprecated, safe to delete. diff --git a/cmd/config-migrate_test.go b/cmd/config-migrate_test.go index fdd043fcb..fe4054255 100644 --- a/cmd/config-migrate_test.go +++ b/cmd/config-migrate_test.go @@ -17,6 +17,7 @@ package cmd import ( + "fmt" "io/ioutil" "os" "testing" @@ -153,6 +154,7 @@ func TestServerConfigMigrateV2toV18(t *testing.T) { if err := ioutil.WriteFile(configPath, []byte(configJSON), 0644); err != nil { t.Fatal("Unexpected error: ", err) } + // Fire a migrateConfig() if err := migrateConfig(); err != nil { t.Fatal("Unexpected error: ", err) @@ -191,7 +193,7 @@ func TestServerConfigMigrateFaultyConfig(t *testing.T) { configPath := rootPath + "/" + minioConfigFile // Create a corrupted config file - if err := ioutil.WriteFile(configPath, []byte("{ \"version\":\""), 0644); err != nil { + if err := ioutil.WriteFile(configPath, []byte("{ \"version\":\"2\", \"test\":"), 0644); err != nil { t.Fatal("Unexpected error: ", err) } @@ -245,3 +247,39 @@ func TestServerConfigMigrateFaultyConfig(t *testing.T) { t.Fatal("migrateConfigV17ToV18() should fail with a corrupted json") } } + +// Test if all migrate code returns error with corrupted config files +func TestServerConfigMigrateCorruptedConfig(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) + + setConfigDir(rootPath) + configPath := rootPath + "/" + minioConfigFile + + for i := 3; i <= 17; i++ { + // Create a corrupted config file + if err = ioutil.WriteFile(configPath, []byte(fmt.Sprintf("{ \"version\":\"%d\", \"credential\": { \"accessKey\": 1 } }", i)), + 0644); err != nil { + t.Fatal("Unexpected error: ", err) + } + + // Test different migrate versions and be sure they are returning an error + if err = migrateConfig(); err == nil { + t.Fatal("migrateConfig() should fail with a corrupted json") + } + } + + // Create a corrupted config file for version '2'. + if err = ioutil.WriteFile(configPath, []byte("{ \"version\":\"2\", \"credentials\": { \"accessKeyId\": 1 } }"), 0644); err != nil { + t.Fatal("Unexpected error: ", err) + } + + // Test different migrate versions and be sure they are returning an error + if err = migrateConfig(); err == nil { + t.Fatal("migrateConfig() should fail with a corrupted json") + } +} diff --git a/cmd/server-main.go b/cmd/server-main.go index e9378032f..ac3efa6a7 100644 --- a/cmd/server-main.go +++ b/cmd/server-main.go @@ -107,7 +107,7 @@ func initConfig() { // Config file does not exist, we create it fresh and return upon success. if isFile(getConfigFile()) { fatalIf(migrateConfig(), "Config migration failed.") - fatalIf(loadConfig(), "Unable to load minio config file") + fatalIf(loadConfig(), "Unable to load config version: '%s'.", v18) } else { fatalIf(newConfig(), "Unable to initialize minio config for the first time.") log.Println("Created minio configuration file successfully at " + getConfigDir()) diff --git a/pkg/quick/encoding.go b/pkg/quick/encoding.go index a96f12314..a5100b6e2 100644 --- a/pkg/quick/encoding.go +++ b/pkg/quick/encoding.go @@ -21,6 +21,7 @@ package quick import ( "bytes" "encoding/json" + "fmt" "io/ioutil" "path/filepath" "runtime" @@ -55,12 +56,15 @@ 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) { + switch jerr := err.(type) { case *json.SyntaxError: - return FormatJSONSyntaxError(bytes.NewReader(b), err) - default: - return err + return fmt.Errorf("Unable to parse JSON schema due to a syntax error at '%s'", + FormatJSONSyntaxError(bytes.NewReader(b), jerr.Offset)) + case *json.UnmarshalTypeError: + return fmt.Errorf("Unable to parse JSON, type '%v' cannot be converted into the Go '%v' type", + jerr.Value, jerr.Type) } + return err } return nil } diff --git a/pkg/quick/errorutil.go b/pkg/quick/errorutil.go index 063af3d5d..2c0bc9759 100644 --- a/pkg/quick/errorutil.go +++ b/pkg/quick/errorutil.go @@ -21,28 +21,22 @@ package quick import ( "bufio" "bytes" - "encoding/json" - "errors" "fmt" "io" "github.com/cheggaaa/pb" ) -const errorFmt = "%5d: %s <-- " +const errorFmt = "%5d: %s <<<<" // FormatJSONSyntaxError generates a pretty printed json syntax error since // golang doesn't provide an easy way to report the location of the error -func FormatJSONSyntaxError(data io.Reader, sErr *json.SyntaxError) error { - if sErr == nil { - return nil - } - +func FormatJSONSyntaxError(data io.Reader, offset int64) (highlight string) { var readLine bytes.Buffer + var errLine = 1 + var readBytes int64 bio := bufio.NewReader(data) - errLine := int64(1) - readBytes := int64(0) // termWidth is set to a default one to use when we are // not able to calculate terminal width via OS syscalls @@ -60,13 +54,10 @@ func FormatJSONSyntaxError(data io.Reader, sErr *json.SyntaxError) error { for { b, err := bio.ReadByte() if err != nil { - if err != io.EOF { - return err - } break } readBytes++ - if readBytes > sErr.Offset { + if readBytes > offset { break } switch b { @@ -88,9 +79,5 @@ func FormatJSONSyntaxError(data io.Reader, sErr *json.SyntaxError) error { idx = 0 } - errorStr := fmt.Sprintf("JSON syntax error at line %d, col %d : %s.\n", - errLine, readLine.Len(), sErr) - errorStr += fmt.Sprintf(errorFmt, errLine, readLine.String()[idx:]) - - return errors.New(errorStr) + return fmt.Sprintf(errorFmt, errLine, readLine.String()[idx:]) } diff --git a/pkg/quick/quick.go b/pkg/quick/quick.go index 55cecb5db..e010c8f9a 100644 --- a/pkg/quick/quick.go +++ b/pkg/quick/quick.go @@ -127,7 +127,7 @@ func (d config) Diff(c Config) ([]structs.Field, error) { return fields, nil } -//DeepDiff - list fields in A that are missing or not equal to fields in B +// DeepDiff - list fields in A that are missing or not equal to fields in B func (d config) DeepDiff(c Config) ([]structs.Field, error) { var fields []structs.Field @@ -196,12 +196,22 @@ func New(data interface{}) (Config, error) { return d, nil } +// GetVersion - extracts the version information. +func GetVersion(filename string) (version string, err error) { + var qc Config + if qc, err = Load(filename, &struct { + Version string + }{}); err != nil { + return "", err + } + return qc.Version(), err +} + // Load - loads json config from filename for the a given struct data func Load(filename string, data interface{}) (qc Config, err error) { if qc, err = New(data); err == nil { err = qc.Load(filename) } - return qc, err } diff --git a/pkg/quick/quick_test.go b/pkg/quick/quick_test.go index aed6b1d49..c1b3c70d4 100644 --- a/pkg/quick/quick_test.go +++ b/pkg/quick/quick_test.go @@ -36,6 +36,42 @@ type MySuite struct{} var _ = Suite(&MySuite{}) +func (s *MySuite) TestReadVersion(c *C) { + type myStruct struct { + Version string + } + saveMe := myStruct{"1"} + config, err := New(&saveMe) + c.Assert(err, IsNil) + err = config.Save("test.json") + c.Assert(err, IsNil) + + version, err := GetVersion("test.json") + c.Assert(err, IsNil) + c.Assert(version, Equals, "1") +} + +func (s *MySuite) TestReadVersionErr(c *C) { + type myStruct struct { + Version int + } + saveMe := myStruct{1} + _, err := New(&saveMe) + c.Assert(err, Not(IsNil)) + + err = ioutil.WriteFile("test.json", []byte("{ \"version\":2,"), 0644) + c.Assert(err, IsNil) + + _, err = GetVersion("test.json") + c.Assert(err, Not(IsNil)) + + err = ioutil.WriteFile("test.json", []byte("{ \"version\":2 }"), 0644) + c.Assert(err, IsNil) + + _, err = GetVersion("test.json") + c.Assert(err, Not(IsNil)) +} + func (s *MySuite) TestSaveFailOnDir(c *C) { defer os.RemoveAll("test.json") e := os.MkdirAll("test.json", 0644) From d13aa1c42dca8ca58bd7954658784e59adc0c56c Mon Sep 17 00:00:00 2001 From: Rushan Date: Mon, 8 May 2017 06:32:19 +0530 Subject: [PATCH 08/80] browser: make input number types readonly in share objects modal (#4273) --- browser/app/js/components/Browse.js | 26 ++++++++++++++++---------- browser/app/less/inc/form.less | 13 +++++++++++++ 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/browser/app/js/components/Browse.js b/browser/app/js/components/Browse.js index 82acbc1b5..b4cbb769e 100644 --- a/browser/app/js/components/Browse.js +++ b/browser/app/js/components/Browse.js @@ -722,11 +722,11 @@ export default class Browse extends React.Component {
- +
Days
@@ -735,12 +735,14 @@ export default class Browse extends React.Component { type="number" min={ 0 } max={ 7 } - defaultValue={ 5 } /> + defaultValue={ 5 } + readOnly="readOnly" + />
- +
- +
Hours
@@ -749,12 +751,14 @@ export default class Browse extends React.Component { type="number" min={ 0 } max={ 23 } - defaultValue={ 0 } /> + defaultValue={ 0 } + readOnly="readOnly" + />
- +
- +
Minutes
@@ -763,9 +767,11 @@ export default class Browse extends React.Component { type="number" min={ 0 } max={ 59 } - defaultValue={ 0 } /> + defaultValue={ 0 } + readOnly="readOnly" + />
- + diff --git a/browser/app/less/inc/form.less b/browser/app/less/inc/form.less index 73edcde11..d58e22ca7 100644 --- a/browser/app/less/inc/form.less +++ b/browser/app/less/inc/form.less @@ -183,6 +183,17 @@ select.form-control { .set-expire { border: 1px solid @input-border; margin: 35px 0 30px; + position: relative; + + &:before { + content: ''; + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + z-index: 1; + } } .set-expire-item { @@ -191,6 +202,7 @@ select.form-control { display: table-cell; width: 1%; text-align: center; + .user-select(none); &:not(:last-child) { border-right: 1px solid @input-border; @@ -209,6 +221,7 @@ select.form-control { left: -8px; input { + .user-select(none); font-size: 20px; text-align: center; position: relative; From 0d9de50e21ea77b8736e3b5ec2d8900520b2b921 Mon Sep 17 00:00:00 2001 From: Nitish Tiwari Date: Sun, 7 May 2017 18:58:24 -0700 Subject: [PATCH 09/80] Bump Docker compose file to latest release (#4271) * Bump Docker compose file to latest release * Bump Docker Swarm compose file to latest release --- docs/orchestration/docker-compose/docker-compose.yaml | 8 ++++---- docs/orchestration/docker-swarm/docker-compose.yaml | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/orchestration/docker-compose/docker-compose.yaml b/docs/orchestration/docker-compose/docker-compose.yaml index 5bbd51f46..b050ca8cb 100644 --- a/docs/orchestration/docker-compose/docker-compose.yaml +++ b/docs/orchestration/docker-compose/docker-compose.yaml @@ -5,7 +5,7 @@ version: '2' # 9001 through 9004. services: minio1: - image: minio/minio:RELEASE.2017-04-25T01-27-49Z + image: minio/minio:RELEASE.2017-05-05T01-14-51Z ports: - "9001:9000" environment: @@ -13,7 +13,7 @@ services: MINIO_SECRET_KEY: minio123 command: server http://minio1/myexport http://minio2/myexport http://minio3/myexport http://minio4/myexport minio2: - image: minio/minio:RELEASE.2017-04-25T01-27-49Z + image: minio/minio:RELEASE.2017-05-05T01-14-51Z ports: - "9002:9000" environment: @@ -21,7 +21,7 @@ services: MINIO_SECRET_KEY: minio123 command: server http://minio1/myexport http://minio2/myexport http://minio3/myexport http://minio4/myexport minio3: - image: minio/minio:RELEASE.2017-04-25T01-27-49Z + image: minio/minio:RELEASE.2017-05-05T01-14-51Z ports: - "9003:9000" environment: @@ -29,7 +29,7 @@ services: MINIO_SECRET_KEY: minio123 command: server http://minio1/myexport http://minio2/myexport http://minio3/myexport http://minio4/myexport minio4: - image: minio/minio:RELEASE.2017-04-25T01-27-49Z + image: minio/minio:RELEASE.2017-05-05T01-14-51Z ports: - "9004:9000" environment: diff --git a/docs/orchestration/docker-swarm/docker-compose.yaml b/docs/orchestration/docker-swarm/docker-compose.yaml index 4854c449b..a58aac5e4 100644 --- a/docs/orchestration/docker-swarm/docker-compose.yaml +++ b/docs/orchestration/docker-swarm/docker-compose.yaml @@ -2,7 +2,7 @@ version: '3' services: minio1: - image: minio/minio:RELEASE.2017-04-25T01-27-49Z + image: minio/minio:RELEASE.2017-05-05T01-14-51Z volumes: - minio1-data:/export ports: @@ -20,7 +20,7 @@ services: command: server http://minio1/export http://minio2/export http://minio3/export http://minio4/export minio2: - image: minio/minio:RELEASE.2017-04-25T01-27-49Z + image: minio/minio:RELEASE.2017-05-05T01-14-51Z volumes: - minio2-data:/export ports: @@ -38,7 +38,7 @@ services: command: server http://minio1/export http://minio2/export http://minio3/export http://minio4/export minio3: - image: minio/minio:RELEASE.2017-04-25T01-27-49Z + image: minio/minio:RELEASE.2017-05-05T01-14-51Z volumes: - minio3-data:/export ports: @@ -56,7 +56,7 @@ services: command: server http://minio1/export http://minio2/export http://minio3/export http://minio4/export minio4: - image: minio/minio:RELEASE.2017-04-25T01-27-49Z + image: minio/minio:RELEASE.2017-05-05T01-14-51Z volumes: - minio4-data:/export ports: From 85bc6003e9c43a010d461a64ed6227260e63d6b5 Mon Sep 17 00:00:00 2001 From: Anis Elleuch Date: Mon, 8 May 2017 22:42:05 +0100 Subject: [PATCH 10/80] gateway-s3: Avoid x2 double quotes in ListParts (#4295) ListParts response returns doubled double quotes in ETag field. This commit cleans ETag when receiving it from minio client to fix the issue. --- cmd/gateway-s3.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/gateway-s3.go b/cmd/gateway-s3.go index 7c4c8aa9f..f2be3f269 100644 --- a/cmd/gateway-s3.go +++ b/cmd/gateway-s3.go @@ -449,7 +449,7 @@ func (l *s3Gateway) CopyObjectPart(srcBucket string, srcObject string, destBucke func fromMinioClientObjectPart(op minio.ObjectPart) PartInfo { return PartInfo{ Size: op.Size, - ETag: op.ETag, + ETag: canonicalizeETag(op.ETag), LastModified: op.LastModified, PartNumber: op.PartNumber, } From c6258f5e97a336f12eec009feaea9f80c5d108e8 Mon Sep 17 00:00:00 2001 From: Nitish Tiwari Date: Mon, 8 May 2017 19:22:34 -0700 Subject: [PATCH 11/80] Multi tenancy doc (#4215) * Add multi-tenancy doc * Multi-tenancy documents * Remove intro * Update deploy-multiple-minio.md * Update deploy-multiple-minio.md * Update deploy-multiple-minio.md * Update deploy-multiple-minio.md * Update multi-tenant details * Remove file * Rename deploy-multiple-minio.md to README.md * update ports * Add multi-tenancy diagrams * Link diagrams and update disk name in the commands * Fix tenant config directory --- docs/multi-tenancy/README.md | 53 +++++++++++++++++++++++++++++++++ docs/screenshots/Example-1.png | Bin 0 -> 99540 bytes docs/screenshots/Example-2.png | Bin 0 -> 108134 bytes docs/screenshots/Example-3.png | Bin 0 -> 271810 bytes 4 files changed, 53 insertions(+) create mode 100644 docs/multi-tenancy/README.md create mode 100644 docs/screenshots/Example-1.png create mode 100644 docs/screenshots/Example-2.png create mode 100644 docs/screenshots/Example-3.png diff --git a/docs/multi-tenancy/README.md b/docs/multi-tenancy/README.md new file mode 100644 index 000000000..2a4fdeaff --- /dev/null +++ b/docs/multi-tenancy/README.md @@ -0,0 +1,53 @@ +# Multi-tenant Minio Deployment 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) + +## Standalone Deployment +To host multiple tenants on a single machine, run one Minio server per tenant with dedicated HTTPS port, config and data directory. + +#### Example 1 : Single host, single drive + +This example hosts 3 tenants on a single drive. +```sh +minio --config-dir ~/tenant1 server --address :9001 /disk1/data/tenant1 +minio --config-dir ~/tenant2 server --address :9002 /disk1/data/tenant2 +minio --config-dir ~/tenant3 server --address :9003 /disk1/data/tenant3 +``` + +![Example-1](https://raw.githubusercontent.com/minio/minio/master/docs/screenshots/Example-1.png) + +#### Example 2 : Single host, multiple drives (erasure code) + +This example hosts 3 tenants on multiple drives. +```sh +minio --config-dir ~/tenant1 server --address :9001 /disk1/data/tenant1 /disk2/data/tenant1 /disk3/data/tenant1 /disk4/data/tenant1 +minio --config-dir ~/tenant2 server --address :9002 /disk1/data/tenant2 /disk2/data/tenant2 /disk3/data/tenant2 /disk4/data/tenant2 +minio --config-dir ~/tenant3 server --address :9003 /disk1/data/tenant3 /disk2/data/tenant3 /disk3/data/tenant3 /disk4/data/tenant3 +``` +![Example-2](https://raw.githubusercontent.com/minio/minio/master/docs/screenshots/Example-2.png) + +## Distributed Deployment +To host multiple tenants in a distributed environment, run several distributed Minio instances concurrently. + +#### Example 1 : Multiple host, multiple drives (erasure code) + +This example hosts 3 tenants on a 4 node distributed setup. Execute the following command on all the four nodes. + +```sh +export MINIO_ACCESS_KEY= +export MINIO_SECRET_KEY= +minio --config-dir ~/tenant1 server --address :9001 http://192.168.10.11/disk1/data/tenant1 http://192.168.10.12/disk1/data/tenant1 http://192.168.10.13/disk1/data/tenant1 http://192.168.10.14/disk1/data/tenant1 + +export MINIO_ACCESS_KEY= +export MINIO_SECRET_KEY= +minio --config-dir ~/tenant2 server --address :9002 http://192.168.10.11/disk1/data/tenant2 http://192.168.10.12/disk1/data/tenant2 http://192.168.10.13/disk1/data/tenant2 http://192.168.10.14/disk1/data/tenant2 + +export MINIO_ACCESS_KEY= +export MINIO_SECRET_KEY= +minio --config-dir ~/tenant3 server --address :9003 http://192.168.10.11/disk1/data/tenant3 http://192.168.10.12/disk1/data/tenant3 http://192.168.10.13/disk1/data/tenant3 http://192.168.10.14/disk1/data/tenant3 +``` + +![Example-3](https://raw.githubusercontent.com/minio/minio/master/docs/screenshots/Example-3.png) + +## Cloud Scale Deployment +For large scale multi-tenant Minio deployments, we recommend using one of the popular container orchestration platforms, e.g. Kubernetes, DC/OS or Docker Swarm. Refer [this document](https://docs.minio.io/docs/minio-deployment-quickstart-guide) to get started with Minio on orchestration platforms. + + diff --git a/docs/screenshots/Example-1.png b/docs/screenshots/Example-1.png new file mode 100644 index 0000000000000000000000000000000000000000..7408107d63e10fcd4682469c3be8d8b4271fc7bf GIT binary patch literal 99540 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 z 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 WEzpB2qdS!OZ(3@4s%5txKL0;DwbhFN literal 0 HcmV?d00001 diff --git a/docs/screenshots/Example-3.png b/docs/screenshots/Example-3.png new file mode 100644 index 0000000000000000000000000000000000000000..581886bb4c6c1ae08319f0232760f5cfd1e03f1c GIT binary patch literal 271810 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

U`*TuoN8POMHkXUtk>dE9Mlp% z&-prdQCW6v?*j!k5t_{0%8YfA3m`9S0la?tXspe01#jrBaZ%Bn>q;eMkJZ&KDVDg8 zj$GDG z1y%m6w_~pza2ju87Yka>`?{mnUV?MFEK%*&l;A*R#{R86mzLo}Kk`Y-ebxw{h3~A?@rS+rZLKJ9CU{^$h*xYrOKU*oaQ;Xl zurV1U+PXJp-u|IOwv&nmdTxr3kHSI96b83KnFd*;y`&)6;WcFiO3fAIZ zi7gy3j=yj3aDAk7ZX2anp6j<(q9=GZkv}p2>w&;zVaBp7Sy5nx*FB;KUO+ zj}IJ?l=xV5&G!*@9pXWXt-V!(WS-L@{D4y?TPkLSGx8P{a?SxRZqScoqm-;vwGL9b;=K?3wG%I%g5^BmVOMajRiuN6wmYcird?h zh?a$NFOI@Bj5lty$cIcfU$ySOhN}-| zIv5QErZuTu`?7H@zUM6vvFOEAU)v@2k-jKo#2eW`3EEJ=tuaa4QoP1KlN-lz{8tYxv!%)#2Ft=!D?at}ez41)meg7W?M9`8umziLOWB z{@8=Av2liW{%FrTK3OiUv1!(-l9iJ5q0nXBM=NdPVsk$^SOm0{J#rg_c|Tbb2=K&@ ztgGL6S?%K`6c2&|FGM8t&HXxCT{>SK?>Q+<#Te>il`YFh!Z&@zQ~jNfEp5 zx26$SJbLn-1)rXiyZ!Uj*oYc^mVg!mmD$M#hq5(U+S}Ro&)YbTW@`D%WfnMsE}-!F za<|Lm?emv)zt|Ew24BkOB(dZ2wA%=ammdYafAU(NfsbgBdt$6G32(JJM|72=XtNVu zjx2o63Y&TN6(&=gZtlpEHK?&Oks#yabks_^?1Spvmh;h~TSsjdjwg{UoN`Ha)4C6C z4Bf7Fk1JdtNRQpGpV82%asD+KM%C@-Fg2h^y<9>vq;wm_;jFP@-ud^AG5F6_rmH=* z0E3}q400#wOK#zVb=60amgwprPw8FNOm~@?sIpsmf?THxmiuFWZo7T)Ce7`+Qun|Q zWw=gmA{zzX=byRU`C9hsMWwIr+GZOHR45&OPECGib||b8DCMf*UP7nhs!p)d=uM;; z>6cjzg~avE)GzpHW5$+025h9xmJ58t>RsJ;U8(UVWv4=Mv4Zutlz( zea?u~B_5jaWtZt~3rXEoNKjz$z3MJWv&>fL|q2d!)Jqg>QY3USJ zDx$&2Z$GU&WNN6+!$4(s&t3kF_;A_9A?ZWi*p8de>DpMtwAVAuOC11p=0-|=Gvn5< zja!C6$j$ywPexEXTNE)e;+W+e1%4(^?0mJc+txBl)i=YtDE1+lK;8`@w;!;@RXS;} zu|y({+7ksyM{Xk_-HQ2_-CCx~*F2kUHP%BeXhd^Ty)8k_eWe%X)<+iFwa##n!x81C zuZh@XZ`?~{<^DE`e;lFZ+!=KPec1H{QRgxi5v|3qL*CK4%Kz6XieIs@t5z-iu`WZX zG)75S*M~9~@emP2gpZOZWM~N~3+G?+J<9Qm+eE=G zFw_(2mdWv%`CdE7I_ZOs>S!&*3u&@?Qy?~GRa$P;M6X_D*H!_oYdIr1Qv6 zJU{UTs*Ul2qv$?N^aASvrKw4P<6;07pDU4v-x}#Qz-`@?iG|O#|DN}NJfXYJRV`i$ zF7AC$0!1CnB=_i$L#&j-zyCV(gTk|`20G1)A04o_QH|qK|C2;bvP%$`!Ga@hmA)E3@3sGOEDequ40Nvke!LOG1aKha!fmhJ z#`Z;OWw_nZ8K(FebQvLXEAQC|P2Eb4)^c`P4ANsy3X^vrkU2+Ywoxx7T(-7U_LckM9Sl9!B z2RF)!tlYN<#cY*T(9o$l)m8Y>Bd(JLez#_Wr^jV=B=^~+R0&wxo#e_T2@OOYFMS66 z82}rX;_4HzuUHT<*qWNT!OhxEu~36m$5GA$BFw{bj~uI zJ6S++(=2!zdB%_AUpKhAn=C0!ilyp_ebG+f=S|ei2SrIBy(MnEn1uUZC)7uHvY+2G z`PsM_lyi*MFF-DYo`vQ_pTTwc?9UQ;@o=|}?l$Sn>kx@?9$&Z(m(m?_=p}T`8ppOJx40c%e48)PMO~}vsVsS_j~{`le$0NVT!-HT{I9nPsB$+!J)u>wL$aU^C$qj+2bsOki z4ceH>Mnf}my$Bth#`rBf7-3gImEX6{p6kZ(7;iSd@C8ZL*7#VqYfvRg{6l7ifY$3@ zPeH;46jrU+wVrDhebY;wH5`KM8wii(iB(5I5$s|ms1Nl1QoG@b3jAJJ8eEUhuk1VA zMo6EPkY$-MBw@;2;&ZA0K9Iwt-fHiNTj}!YELER9xBohE`r2jH;WM(5k?-3c4gs#a zBauRohI;U!ZBZR@LzJ0KyXNwR*X7C5=phEA@v)8bP`}A|b~z=rQ*j&Rqwx*(^y`r1 zJP{2E{_~BT7q1ZFy2mC~7a7_@Uuk$s6T9ZBs7Vo-4DToQzJiih2OHxwJw8tD%@geL zIJcsT?_Tg?xt82l64ZuK1ngjE{fE5xrs1C+`mVp(qvap0lPq3?CN^HDdC5LPDHbe>%$JYFV**s$<(5l-eDc{9&JQ+`AMo@QGFczDQg117}KnnYO;wRTaUvH!@asD zFHm{pDCw+kZ6YJm;9#t3iK$2_}b2O0AG*oONNGS#pgxTta2gunFE^)Z76U^+s~r^aR?@@YAVk!X{#VoeBMq%wJ$;fBnN>980CL|7Yj|C zSo}F4njylrIUow(YW(i4iqpId;K7Cv?i5H4{D3(sKF?Ix6+6hF8TOOhE%a7pPX#%d zgFDfL#gG#*lD=>>KbvM^zSAD);O<7Dx4F-EvND4}e5GHSKp^KDFH5P^A@#i%_52U_ zp`wFUD`vooen93ky=X7l!Na$Hthl+iM4&5XvVuD^O4R9(f%hE%2SDgvhg+^^_bfSg zXkicpa9v!T^W1$hG@1;pzlbBu-eGyh;#s*zfNeb2uU1h%2A&oKsvV*x(7oimjTVpb z^l|yw$mkST;O0UDZpQin)j3~bvE5Rg03&{;A5b}%8noxT@Jj6utT7-1B*nyy$DYwh zbSWF5laXUrC%CtQ2VA6?3he5^dJ&4R#&aavk1e6Vp`(X`FR}j-Sx#KC8&Vp>+mw)&GLc5Ol`b@NL8uq}#R*us%iA z$F>(zPXa;>dC(C2uwjIzY6Uw`#ZKyqiK#st!YC1z&F5lNHd25^JXo!fwnk}x7Z2rmB!2>e|AY|}^wzDyoZpV!{`7SQZWC`Sf+bO)i zTkmXe`-n^&^k`@+;rn+&Im(V@1+a|qG0}1jIK6tc{0S{nIvB zQoQ*lKM<_hseZxYDd6tY@7Oqu zButmTA>jB@cK;Vs&5HhaJ^Dfk3|f5ZHvv04JH*^DS~ZxmUEa6Ucsr6|Pk)?5he&@^ z1CDZBAnN%>(ahUxGIc&kEQ~b&68N{){o5ma-j4IT$HFly%lcN@xmhJha->{7;vh?) zI9s7OU-r6H_W8|-Pg@p_7n=p!y?8V)m?#9dnF+H#olyBw>4d#z`XQLliEUmssc?07oj^PNmt$OAHAZ+|cOep9Sy z=c{Yc1R{Gt7)cffguDAH&dX~flA_4J#@aI5E)zG>tz-qI(dG@{yl6n5o5k9H8(1KC zfz!er5Cfi9ZupcvAKap)7|&rQ-$lBI|Cl1FE=~9lj+cU|wSA>IZ(H29%SuljPFWZc zt)CH=i&Cj?Kt&n!?-#{11jg*qjCPWP89@)ahJ=8bN!tqs+{ z>?F{o-ebJ)PZz3;MF>iQnl7M$IfH*40tGszEG-+aUi|BA##f!44fioeICkOah6KC# zHP#F$JJQW1=uW4x%*NJ%Ino=aKr;!*^raKQyq||j?p^=B*b(xye8e#4;Fo!!0$IO; zJubd{GMCk7WYe6XP*zpXuc-fhMWj_x_bldEL`Ilm1>eQZVKKGd~S#9+*-;P2i z)xwS1J*v11RrUWq?&QOb>c&(engO+;9AXNp@x+4lMzn7 z46vn$Ct@BP@2B;ew7K0sYKngwE}8A>ohdAn zfGzHe=wca=*CKZVPH>UA9JI0!;4gQRdJMazEzPrwju-`cJgz>$~+CO@>?Ao7q1YzZ$#-od-X&zRLPpj9EW`0O zTWC6naDDGGG*jOR=LkLLNb09rjCwOP)Tb4w!?^b9%agWONMqD}H@V|ID3B2bs_>&`E%Q!zEbjYk5USp&oSE?#S0_qBsp{QTp#?N z`on*xK2%kqWqB|9oh4L+`x~KSLyf2Q#Z-Y;&M@Lsp|0asX?Wr6CxOZ~Y1*ch4vMA= z_MKPy$OA*dB7QS)fif$4?}z33(}`2d0;r4+*c@!)vedmC9crF2+;ky;ZuEnMh6JXn z7`9qQ^V(|c9DaNCfA#T3mI=RWbs`J@qV-)tKHqy`=>3dd_NHG>FqL=Zj^!B}c_XS+ zjqhLmUj4^QzgIu16syJksT3jUFKZTqYWuwA`6J;W8Z9@%#xlz)fi}i<@A8k-?fI~f zOIiZ8Joks!3SzU9S^Ru_wo;{U;jegU0yT0(~X%+;SWAp<~c zZvN&-h=_XEzBMi@ajR80n5}k$XA1Y>tg<6Xk%pNlyKLG~aAxwKgKO`P=nV0p1-Vm4%zI@EJ?S+x|3B!&vh)UHLXFeXX!WZybo z-h{%#V=ESr!B^}1$H&>_{Qu#k7G}N_Ki`6o!=8WL%mOk?=aJ*$Qjg+CxIwZh?6+|H@?Ev#8_#|0vpNbjYp0g-v;q zf?LcVI@d2KMkB^PoPSmI$>P1u25sJJ2BA607Ko4f8>AT#hphzRLF}AXp zER9^4U*yk&Vb(e9ZcB?+d-c}!G;_ttsQWDSVa~Mw>bM$d#10*hei!vwWPkB(`|PAM zJ$E?5c3=0Fz9Uac!B7+f)FAoiDcL6n&xgn>VWuEkjD5z&FqXidRl@5lRyBPb!nir9 zK;2xO5AAMUM0LTT6LE1xZpvx{zNFgcXB;QAi9|$Nh}oF*$j( zsAG55L~k;j%Bs2*oCwR~TIsLNz9y4%(~#wJ)LOrk-rWitf*}%i{bN?XV%w~~i1uv> z?JZJx)KAS3r)Av-GZUV3seDMIk=yTj@avCQW?P21P72-SF5X!_Ez2XFw)^1*Iy_3} zC-2JW7!=4_0IL$2(}}Wn*dW&Iwg`z^!aFGbZ;V~+%)>)v=T_{v=UA(ALq(x=TzKBT zsK#$618(0^0(xG`lb<`=M(cAN-ns4S`B(2H1qv=Z-lToqK3wRfR^{s+y1TuCHFfA* z&?pr6D?Ui8OT1Ol%(7FbRD~aLP860acQ0~j%v07Q31?SCV#C~q;o>jd3`O%ynPc2# zCxxK)T!1C3W)&HwV`~pAe6|An*Ia+33?@Vz4%OncNxDT9_FuABKg9_aTe`h>SnlCe z8L|ircYJ<|UY17~svV?hXHNImRH}8>teZzJwd3>VxYM#e#9VAv6)!s#r^DD{n$i;Au zTetgDpq3|+$wtdUD*{&JiNZsTpW_$o7hkWAl)m%az^b^^Pm}IQS?uSSauMJ~P||5& zcX=%+l(m<4h0F7jicNKQR-L9~vaZxyJ&&0ho&8j3tn2NJ*2u3;>Cy8PwaBHkB6`;g zLrE^Tp!ALIbA24fbbl^`(pYQ-Yp#DwpI75jN>&yO_FO(AyGYfi z@5sq&=?*?735F{O8j;ty*11Ubw9bn(V;-N#&$HwI#q~ zx1?6M#v~uByW2N@d#`PTqfN_AshPP)_knDv-}WSfm-NZF@4xCBSc=)F-j{7_bRXKI zBnIM!x_mL{2+PeT_G`E(O_gZrGCdukb!OW2wJFcLmWOX3BITYAEjE@)N|8Oj;p5M*P37HZ zP%AJ?#!^8(QJjJPaEgdwbNMwl#4jya1N0ICy+4hL7*_H-i0JCB{nanmQ)jOQ)Nol| z1&9WE2fxMepB+YHLqoaPE1_l|vO6^s_DW58>tJ0YVE2Hmjmiva9D`DhN)Yeu51NwM zHEY^Fa1T%|Bo$*1F-Qa9!>=#1fpak7B~1=Zgl<&|Y(fzv*)BzMDDTSf&PxV#<-&&| za~1C=mgL^RrjOSIwL5Q}C#t7YA;Hz;!;|vwzWb{DnqhDo{wQz>g`*e^`9jmUkllXy zhQs)LlB``fz_-WPdJHcgp--><z)QrH+O4M8+gx-SRAuWx{j#I)*{YaP4p0gm6-zGy7Q09bVZD?(h4Ft;~io-hP4 zhso-3R%G*yshaR16b~#9sag(A^rt765db~{yd(U+yX?W`#UwhaK)E5oG-85YU5VT= zB-v)BOs6mOI2j%MNXCSTLbudIp~ik+1&(8#daM?hvS6nQle32%_UEf%)V*y5Q>~~} zazsMTLq2H80iGAGQL_pBZcc(}1dZ#|eQz zEeY?V_J+3?x)iYzYt+Zq4q|g91jkf#`@RW6<{JaQm{e84E3o1RmTd{p)@r-kA$9GaOgexcX0TrkGEkbLjvaia%I-U}6g2*N!p93? zjscvS<+WNi2XxGsXtA9od21-;rK<4 zm131fba$dVXV=BQ(I0!=DeBMv*4D|);gForyYrv-G+ShEYvlcoq{FmJ-o}Fn@Lc+T z##DPV#+&i)Qv$QodV{V@OI!XE$dq3w@1ef+=w8Spnfj*;;{DE*gZAdvrIn*Wi*Kiu&DP%MO0 zvfREssSMx6ulC_?Uikd94q*eltKbw|r0Ex?f{Ws)*^L$h4n)ctJE$8mOzu4NSTO1-Mf01U^QkLnn z)#7^L$;lp@AXC$~foZr;7I<4_9v?PvNc^j7&g?X1@$0!4 z@GDfbg>riGsgmP8R%4Fq9EcvO^}Fx=?#VFpzhcFc%n2}#)qS`-IElmEX(~6rYGu9(86&uivrNzxcYq`xJz?|t0{wJ{Q#qw458 z*OPL4yXLPo-9E1E`iF^W8BJH0p0Ux)j-hh9Uc>13&Bq#N&V!E-cDV-+oWY^Myb%3U z-~hlW_t+{F)h?sWp#7rSTh@I0O`LF0BdrzQ`ibqu)hUU;`j?IRs+>;5&yb&btG!!n zUeUJ`IsyIoZu`tF_t-#b=w}yB3SW_zCRdH=cM+VS3?J<8DbJRlIT-qf-Wx*677ar19Cf04uJRw+`eE#H{q8g07%#Ix zLYVtusnWwths)7ZG5&0s*LuKR++WzC?e;r1PFKP-WZ3iAtz8}SoL6rB9jv<82065~ zD#qE`=P6P+*kZ=|;Z>!rS?o8M5nn-V&xE&=K;vBi=UV&)D^+esR!-z-=baly7$?X7 zh>nUn4JylY67-anyO4>Kk7;K=efpG=oz10NWbs5zUq4>I#s(By>;-kcQ}XkjA{#68 z?GyYp4GgY;yU0aEtw8lU<-wPZw^u|zg0ifXp!ibfUVMLlzky7c&qT)Vz(0eqv?<0g zP+u{QccoL^KLYNtTMO!;b@{#baTru3*R^ja?6SA%l{x6C85$KI#4FEk$~$5E0hjfFCv`a9SFor}rrOI=^U&Mz%KMzauV4yBn;$fY{D ziHxb3C5&7Tt$GhArt0m4zCJ%cUx}whjVSvi0dLthD>;%lXK?TE`OZR@j3H+7^T`pwR$le39@)HC_I7O=X5y$K^)dCi$_hOe z(kA#kDp8HEN;+RqNf26*QjZAn=>eruT#a+SfB&(UWWx)7jK99pNt$e0S*+~)5`RyH z;N#*ZlS*VuB!vv?r@@&~nO)1B>ge}L?5TKKDPuX@HE{$(o+5Omh9$reh|{#|?bg26 zSWpj$+Q@6>&E&T-X@^S$J)u5nP@Vo2xgd=zg7b3NCFQqm?=RCTC5rD2s5$#Bef7jl zHS{U4vx2hscNtUmdG_YWr579Q5w&G4b4O`5CwxF*vIbD#JfYfeZ%C*5sX&49q3vKM zxrmerD7jZjtGh=iA)YcER7^`c#N@lto*r80PGot{Dk1Vk6grhDJ7sez6`nvrEp`=G zn=GtS^5fFp+F}xn?+Yl8n4kPOd4%5!OhRfz6%<=Ms-(IDWiw z(syfxnORyDv1?19LXgvm=G1!y!g2(mxK(+=ZkXuQehF@{hcRu+ebTvY-e+vF>uegl z3Y5~CoV*uDGfe5dx>!jrK%UR9K-eo@2-?%(j2AF{xCu(UFHTu7pkfZa-O8O%fob95 zT%N|#m|xF@RCWbZr-&;#hy{kvt1vIW>1$Q|pgN-CrWOd>IC=VN1mh$gua<*4dF{2W zh-AgbJ3m43*+ru{Ox0?BvIJl%iqt)Fi8c{05&pK$>)8nF!lvA=1G7=@jfJ6xbk{NOigG!d1GYAY>@{pr|AXz|inA^PX`+es-XWhHj{Q<}2 za1dPX%^Ur=C^w4GLbA>&_AANqC zfUKeG$97qpWc_jgo--aL+UAR))R2AMLek~TsL1H)q;c0FkiPIcclg!LTPeKOlbSN0 zbrCds+HxDv=MDVXPDa)21m!goS%18KeGYME+xQ|qRnbG6j#n&)N2lQ!x#)2)Z5I8L z954*%D>b$C>#AS(bWrg?Mx(_ybqj8ILt@*-Z#FXBhq#|TZ-D{%x7vZ~GN^dPM77a- z+h~Dv6=vs%o>6EuyE^spxK#ynW4HyG{N=CgXlFc5AOYaRA&JxZH@z_o=*lelQwg!c z)5|5ax6TkiO+l*NKdk3XfpFV$SCBIGzopWrH z5Z;drz;D{ARB}+IJVyM&C)L<*IOwKwIT~k|zAW?70TV_p7Mz4gT``>IRT4AF-;;QM z@*Pf>Ngc7?0HnKmzGntjfU8ztyReIFZI?DV<9*X`jJ|mD$1=3wbSO=bbL!OZ z<|qrt_~tO##c}$q*cap@k;bdwA6AgHS2Zk|W#q}8mM_on<2DIoL)H;`x`~usbl}O4 zRY#UP_pa3yv^&|E1}I}FV;0PZeZK#^(szcLm(iYZQ&f0*Qn4Po*;?%xpKQ@KnX`W^@TO}wQP>C$2T$AfFyYh8O zRAJ%U=L>pqjbW$j;7|=VkCiYs%@&h!6=4;XZ%AdEOmHnR+xa5m{oV{8VvOiyZs|to za?iaj-R1$xs8Ij6=%5wKFD&1E!S!``AkfneooO7~>Z@;POs%S13+$<3NMih{ux|0K zu6ef*8n@mPAY1yoOx`VgNHoyT;)a~!zrSHp8EwSpsIGDxgzB++GE4>k!vCa8j&gf^ zC3o`z-43KN)#NIP&WAm_H#JlWBsqR(51&?QX3{23e(WK;2zB8-bWHXeh$!T}S-7Dt)U&>pSDtEslJ)v@kdmfv$VJ9G%%$apsf-JGR? z_sg--E5*I=khtjayHY@tF&}5@nKnSVKQBa^*FPkqDs4XON0GUG=kLk+nDJ344knpT z`RvR4u1BwPJ|>wAZ+;zJBd+aE9p@IPGr#!xE#rrhj2M}Qgr8^%BNtib}b$1FRl0$6#XerKx z^|mtgsCBc+wW7IfeRzVe&w8X(;TAyN*NyQmVIjGDD;!PH*8M+)l%toAF3#i(T7gwb z6BhUk6W?T%OPem4BSt-C+IfIGnd6H(vxy-t9hvvrVMHd7cF~$1ap(g|3>#1;Yy9;k zLeu19-}dg8c_TSnT{;kNp`Nh7OH2+b&RwlYr{g&2?1gGfxYjcPhB8Mj?* z6#?7&7?ve;*}_7wKREhb&T&Nf9z+-`llC4}gm^rv*Z@+@sq)9_qz>KsH17| zxYQ0vNr}I@>A8`aoW|7a0wnIc^mjdzl;SMIn+FOU2}rqzX}$kib9Iq1d*n6WHm2!* z?YYTWlzI7!#d!A6KE8Bj#XfyOBB5r_>!#J!!OOCpLA*rZyaVcYgiw%w)w22nXUfy{ zM`lPd#JVWr^{iD#r>ueP?yR0bW5uMRdry_0uko%~f24bvDYRByEXxEsXA{Iui?9wgTQx*^9Bq%>mm$5kP3?-(z@IW%<=<9M zXE81{X3IirowocLQ_^x6#)hN`t-O8m)UZBcUyi592eQ0mRTv_=VtiAn#jU-gt_}Ga zczTw>{2XFoV{y|WI#m)XffDwNR@-&CKAtk>K{%I0h$!MO;~Ael1FaLmV$QzjncF-> zc{v4)9bcx86mwbs4oy#vmN=P4eVVkQ5qetnIJMrOji7DgJyIiWe)@wlra|lC zw{2`&Eyp*8TMQP6FS-%ArxT`^k8a>eUisu~i@KNYzUsOX*lUgk;-^>{yeXI00;3N|L}%b*dMB2;D(m;(D`{cmQuw3Tuq~F;1+#? zIpM(ru?+LUQJXa49pc(<%Vs_nuOm^?qzT8TPXF%Eq~^UsTG$@REbJzJB=b}SlN&1< za5SY*JviyAY#Iu?!2?i;5 zzDoWXk)11*{haE$@+;ONz?Yk0Lj?yu1SC!!TtwfT=3}?WYk^q`se%YineZETRP$oy zxe%JwZ*Vc=xx?C#E7Gt4s2Dg>Bk~Q702TL2@8^qp2#|dAoqrUb9hu4@7FqZ=a0iSE zw7jBop%dcj>8m`D6PDbwCUpJ)0ugpoae23xFLk~7a=O$wWJBK0Qj9o=Cd!miRX-yJ zoRIRup|ATvng>Vgx=V-m&Sz=msykS#>_Q3<7Nf45W559r?@}|R;}4&!q&-N$Cs{*) zw-!BC;R8r5=bqbT(mD+b;FYCxlWPtS5(tH0>KUvEk^xVG^Q^F7(@DmB*1f~&bJw$s z8r{IVV)?crk5%HBp|SCm8p)*NR^e8E7Bl7#FzL>uFGAZ1VwK|9;&H|!+pk3}Fg0x9 zS1GPDDe}I`@gSYrK>6YdrIHj?-Tc{y(XV;&$A z^t&c+NQrSs%CsGgO|!RA8$p)KT@qMUSogyZ%Wm+kG4T3s^U=_z81-r26gP9>xQ7>z zZZg+a0m&Sva%cysR50^jVOk0KSO`ndohm_qi3G?2fAB<3t0 z;HEK8Fo@(GNCqd+72~ zzjZW1$8CVSE+Zy=iZC(Col+g)2DO^x^Q}kq>7#LgBA8+ z@4%-^*`iS!{bXU5+rJ^!)2l*cjw0PCY$0gipqeO2{2&2RYelUp>yF#lt3Pn& zW5#gw4EPH0vh&iOcv@HW#S{-qP4a9P$G>-9_h;yFSa-qmQCOk*C?)*~zGSeMfJsVsLl(b|&NscioXsRu$U_RR;~))=ZqMp#0&e6A@TvsNCd%+?;$vGj$!iRZx?$OLS@ zs9(#!;M(yn=d|!n<~8NJQFC$3<1tDu_r&w&9@#=ep(`98|f;lofl}YRGN1JiWcW7R5BX{E>81598Z4N;O|NHs%UP1EZXwDHYY= zY?qcPMLf!Coh6v$2yh?PvhM^~{%{)(>RGO^LeWdH1b6$K(GJId0e7vqv1$JnK-$Wt z9;yrgb_e7s`gA*PZp=zr@^R0jy8h_GC1-IPEPlB-@8qkL(=)qZvq858ZiOfDb?d@} zN7KcfF9*MDt8W9gVFT%s`#v z!d1zev(*9R6N?c7e2Zo@FA2`lY3pfjO3(F|1FklLPLJ6VS-8Ke$091)jK-I>Ri0^K zZtDKAe*9@kVg7ZK=@H~8{t4NgECf=KDQ2dDgguhI4CfHiD5mED|J-}k+=@L_|Ky&fL zHw2O3d}W#b%v*XWx6!`*EGdu!IbUzjDVDiB0{*-)IcP*NfGKsbho@D3G|BWSFhf9| z&`IlnPpQd<13RUxo^NT~um~(^!uu)+jM+x>>92qC`iZK%wyk0Es=wJe6UcVrEp%t+ zandMOmNQjBYA$};;C*-SKAes$H+8-9$o_Rby>=x(0DXW<_<#7j`W~e(Iz{ypnU{5AN{7SeQ3e`J1UX=ZdjinXxCc{dF7tjYEoDFbpX6eYrG@Ut_w z1RN;}-H=&~Z9Y4;$sgxLN;BVx0~z!th*pk2T#|GSTnMahWdBGZgto{Aa!rNxU(`va zxE@vT2$jR&S*-JnY|n{SfhdXK`^)8m7EL7!J>KJdH^F%;_4Sjr8sgP+&ro9k`c$g3 zts(G~^5n~(?6QQrc^;@A`hf~1WMsz_Wuz!1?3lVjZSPAif53b~9nTCcao&vJX)LG& zeCu562n*&dYIHY@auj2dD>ahomo@0qj8Gc4hY|BO8H0z}=+3Xm3-e5Dp{M|}Q`)fi z#j+YX$)N@krB$It+^8oaQ zsbaYhmCxK}_ilNGxamuJ*SJ*%%Q{Sk3l|@$o8}*8f?Q`Dj-4AT=uV(F6>VeNs`v4cU_1T|xqKaLz;eY-qmDMbiNn7VVLoJxdDh>#@^Wxn>d_*r!&I*gDIPZxJD75)0+ z^tjBg#p~*z=Msm(8kHvOXyn%MfbGnyF<`xCEtU}STe@GODYGedDULKQe(e6Y_l}1P zmJJ+rxr@>zf;=Oz_o~aEW&-qVG{DwQ??IC~(O#UMLik3qx@@Z7m*0Lu5DNwwF-b}8 zR*zSv3cO9DQo^7=0M0#CXHg}QDl)m3C-BgLaZq7Hf zFdbsyV;cEyviakE!n(l48i5uxd8Tfa1P2Mw-C|i;s^bt5nyOxACG4Z$N9twlwwO;3 z{siFh^jdyTF@Yp{rDX5bOc^B(@U}H4&|5H>Jn=x(?byL_;kW>nOzzrr5fKKu><4)c00 zMnWV}wGTY+mqkFdEw)gFiGZ#1Qc)fY1#k@EmybUPXhzltzcJ$Sk~q*B3x^}qvIR;T z*3(es08-f`r0g8_8q9XJ#wG=oTS{5`HkHbLGN#0v`@F$QdDU$maj97yYWPl8H{^@5 zZ@}}W%n=FC4U6x|UbIL3&vwkkc)K3uC80Z|u4Fq`KZa%+#F#N*;@gpNhWgKE?Q0PI zxZI7B??)@oBUX5W?0nVu(}T^zJCR*qn!W-db@Q7e1xW;Xm|?U0h}~dU3y@%3bvL7Ua>7 zioD!!^Lz7^mp>Mt6sQ1}x}ijzvN7V&@wg%j&*I8^t?TOh733BMZe-UvS+9L6)TanX z8UK#W8KvCtRuPoq;WTDa`>C@!z|+KK&+I$Zv^e2(_wjyLpRVY2h_SW$`V{4#Z+dG@ za)+}b*8%fM85w$*>3)Un-C}Y_c9WljskUR#!HmNeBB^vrEofn#^;u*mckMKzIFNZ= zixi|aPL-L~xhoA}ao{HkNuda&4K)NGlf%*T(ePn#CUILB@UcdLIMG{i-4GAZ_L`cB z_37IlvoSvRqe0=ffTwgWz1!jOBsH4=TTsQcF8~X#GK9`oq-#F|;X?OAlW?Cr$3N_gqv4IRs_-_pAv9{;-z1oB=kf z{{{e+5PLAkdl&uKc8NH!!7npPXmAU|nf}s+c2HiZrH4QJq28J?i*;A2UqXY3<4Nk! zWjmoS-|l2?SipKj$_SC%-EfkfnAKlqab{_u4fuUeZJ#sL#hYcPR+5tj%T!@*|2|fo z`~fq|l!ShSZ)--cSmz?EcEjSyp1J`(I1r}Y#pt>BbCm-NQ!@YY8IIJM%#&$n8Q#|6 zaWk{$b{*`Xah>G5_pxwHpAH9oxkr&B4$m*1u!j6}4Y&&)9HFu+mr0A#u;g#xNdjr* zF0SP#K0V@DVWb+_0|xm{q`**bl*mdbYFf!IEKCGRi`z|M_)a6up{kQWJjX!D`K9UO zHaC!3pIo2P>V35p>$P;bXbYyBvB>W(R*p&ilRGn2oXrlx<4?K;Jr*W@6$!_j1Wl4JbJvvvi=i~CzDK-a(szIR{?z;InpZ)hs1Fe5@d2Bc;=`3udIEZ9+5Gyps^vyEGC6D#Cw3GrU3Jl?mOwR z|H{&y?RA7O;zi4VhSKG!!fpcV8|(%3`M?O(r!Ka(b%OfO;fEWhUNBx4&K?ogqcFZ%QxW|xoz zB-Dfx10b@_i6i>NaYhP#B$dy!&ISO8tS?Cf9-qjqQV1HD(i|k{P{Er6!}947>#NIe zR;-Ij1HHq;Nw;sVm(UWI-``5c+l;|k2bDDQJy$j)PaCRDcDIeuUP7LSYv6k!?F7y4 z7sKeQ;hoPRv@a?W=l1JYqpa0cC2t;{r0MwHN4{BoS^-v_wmO|{cjXN(jOmE1jlVNl;(VbOTi#dv=z_^|C_~h4_$@FNY#rGfM|XGT-TUs!6~aC+CkN~>giyzE z8`Sd|=Hx!`H@z!&7er=Ne%8h78yoj$!F{R4Ek5#Jqa`Cjy z{NZ%pzt*9cYJ}HvZ!Vc=HJn5M1YJwu_ifzlCOJMW4+~frRsLh5Nf@*NurXD(6dj>~ z#|JLuum&p#yf0lDd%d15O8yHy2?do>Wd6C1O(_?=!vhy&lKRaB@K$oNH*Qv?ZkR3s zmp|1MqepM->YJP>?hsg(&+rS?VAVoozMPS8Z=L&`eVsqts||g%28iiEMGS{#ZH~_W|N)k)vRR?n2ygE>sD#DFl^OB zxKt?Y5SPD0q%tzy7d#`@I$SNjr~^4z45sUVbB0pN&+Rph01X5goOb7sP^o z2-ZWBjQxHAa3g0LHW2Bw>3Pd=p;`7WEmda5+Og%yUp}5?z`NI~4pySM*lXG!x+S+z z_43@gI~OGfFF}uO4oXpeerxV)pL?iXl>3#!1dld`)%e5j5D=?11V3-PgWtaJ)uo$* zase3chg!4|kPJ%DsR;&==c#b(0UT^Esk+auR7&fpoCr3+zFyD#A>3KWiQf*3<%tfn zx_5;wBMNVEtVv?7shYJZN;E`Mis+?c*8(gwq^oQ?R-wZeU|DkIBeSC=#b|tp`@D8; zBFr|#JP{Ci>Y|T2Y;F(S8VLr;m})*zYw;}}M7f_)=xu%0Gt$1G`Uygi<3i~e-c}OM(6D*Lv^F>oCS~fRth~|6k5v%w(eDDktbK3SI=m~wxcRlH zw4Xri*C#vR4bmi8F3%3@2m;2k=495Em-~`(xc4ZfqcgbL5Fm9v=_WUadz=nB`U?1{ z@(V3}7_bl3xSEARuePiaKIdl$UP(k%h3X~$-={2raR0Z{mBtE*uq_pjIDWtDfJas3^aWmnsmC3TBwi&YbC zwMRE46Il4R*PKFts|crh?S(HaL_Pk!i^Jgx(PLiu`1s>KuuROvu_$S3VS8HL{D~BK zkR5-zJ1-fP6MZG-Y_QdtN&Sj0Iz4M$XzX{hO~a|?ky&9~FegIuHGR@k29Xtx<5zjo zV2`D^Z<20b ztGfqmKeY3998WF6yx#P)x@J>(RsWNs;$$_x0q;Oss0WxwecA)4tx{k}gz1rd?V{JV z1*=kwlM59tGxgMbU&Q?`Yg}@$C-PnO=i<(x-B_1~cVp+QA3Oi7CPg$>R5cJ_h^yuV-A~laD@E3N($yvz-q< zyS!Z=I1qmBVb$N+Tj#jm4=*(*S9mGqmHQ<6+&XTE`9<`xi?fV_8VD$9u5H&i6CGldh zVs`sj2$q}C4eXIK=h&dr6UJV$nB5{dI&#X!xwmanb0X;-p&I2#$8|Vh)eUcpZ&t{U z&CFgF#c}{>LVe5e$EOTsKP7pae?R-|g<1D=gP|^0Rl0;BuLb5e$&vPUV~S(!+axE- zG*2bntpng#ckL?eD$t-3f5@qB_re=-Xv|BqjhVgN8JU>4y>G&X_ky9}6 z3yg11fts28h_bnd4N*zp8rohu{sbW}@VhV{k!CZRb%DwsyAH`t>dcw}%w+2k@Cbgd zcHipvwM}ZOHP?>4;4I15EBX^4J>!hs0iNIs(|vJHNeg9{CDM zc*Q(IYfai9&qx`+E?!1F10~P3ev{cPPn&h=v&KZ%Hzawv^X@v@BzD2~+UweD^cGZwZv&+`RH-I>(4h=%#8O%BQm{B}XxNlCZp z1;0=JDslblld6@BeJm;unb(;s$ZQ)ZHKhbu!utSrQTqg?rS{0T6gdnKLJ*dI>OrJ~ z49RT-E6(ygv-k)}s`jv0OFsMgl+nj-xV|l*3PKn773D5=jT?n#jvyOchD6yOd2+A{ zhwmd%z(~x;vlx~_d)ER=1d59Wc3;T#pK_jkzlk8W*p!rX7ity`xFk1m1}O0z~u zSKO77>6wW=D8O`l@5yo3Qoe~FR~ns2dt9G6uqqgWw$$71q)yo`ZXcbdU&xG3Y3(W4 z`_HFMnH|2IWME_4pxF^)axLiG-WO(_xXEAz;>BAJm4dz&rZ@b4>2eXZ_KHp|%(&Tj z!;Ad*b__-(Zy@iK7HXLNdPj3eI{8`kTTL}(?YLr<Zk!$0`(6 zi-#KNK?3N>RQ)ghX{&5_B@knnHF)5f(y3Gf6qnlg2vu6(+-FH-_kAG?C*u`RC*v;* zdW74d3#nRI*ExeI#2xWEw%f?r%;pt@jrKjjck=!mt~v2u)QmVcB8>-g6&z2k%eZjgNQ>kK*~_By&WubZgAV4_?TH(+xw8lxO>tO<2NdynRm>-1G5#44iB!)HvLT z)WTW@K>-ak54?EO{a0P{dx=%!`n$R-ezeA@Np>NPd4~9p?Et_8f0b%Df}?FM6scwU zDt7uAPm{-iY2I|s*$X=+#G?1nU}3?4`6unJ<>?(62#fprGh^i|ixtA4C>7xau%Wpf zh45;<_@1$4WG+J|wh}%Tn$^Zn4V`jXmx9JiL=Wg&A07u=Q=xmzlfy{a@-7d2_W@Z^ zk!Ey!4o{ofm9g`~G@G2y%Pj{^gq-xztNhm3tnODxHc9>%!l@})5ByxH|DX^I;!^|> z$io=#z5Gn*!#*KYZ(Q6tI-FT7+ll2Vx{Xo`p@Mq&mZ;t&fFb+*XOu&hwI0QIyv=+P zLip7vtSws%K#K`!;LSMByeU|-kG#4MU=~sJ2T{Wl!W@6yIj4xeaZ^5cV zWglyh&C(hilK=EQ8Bm3Fy|K6r-DvMfjh<8<5c2^OXBohz_h*C3N)3L5L+|VnB!Sa3 z&el9uILn%!?#`MW>Q&{LlkDF1>|H*&;L!Mz6?XhbWTLe*mg)X?Ik)YA1Ddi@&KGyN{VMqoA+zP0TWcQ|GMqs)olCsJlt>s?-3_# zR+;LE{9+l*`7*!j?LSxStthl^*P2Nb-8F!Jj8s{^fpYS!5b?))^dk{!ei!3~k3WxM zmDEcs5GWb-Jr8$lV5~SSZQ6SQOQ(Eskm}J7b0dpAH)=}xeme6>hPGuwTi=4;Gr7ky zuzSA2MGI5D3C|Q(5wkIf%6clyMAbP#cuqxk)wI5p&+IDR<^(FgIN#^>WRV5+N?D(U z<6Fax)cT;-&$AEw3n9BUSSakhHuMdOT4q((?q!54EdV9Tlrce>@b?9MPpYyk@W{Co z^PBh&DrSP;tneO0Ql0^dwnyQCsF0c8iY4@0l*O_T3z{_Vh*H_Wg(Q84nR41^os@Ow zx6%`zWlXwXP=Rvh)y-R=;d+IltydCIc;)9USRN!?aGrCf4>|z4!UVS3_kQvaChc*I z?|h-X8eszl8(+{LJZ5+haRZuV3trnLI3T~U&eO&Bj$eN``;;0=q=I;k^-JUVt$M_Y zm}|&uZXBqbW%-(}6nirfOx9qghTKoz_-UzY+Sh8dn+_DKXId0GuHv(<%-^uNR6TpJ zZ45ZuGk{hTu}5m#BL>cff$xtmg%`^nQOhN`iDR=^*AsV!T5M#vsHQaoi}Yolsa(#%%fECj%g+AQZz&lfw%KyuFxZb_F%;vz=X ztRE6qX`%x#h3JIk{EAnIh7uCwW2A<%68>4qFi zNl=Bb_}+%XoA1dI^$kC*@;fwc=K1$TnK~;M(+eH>n7%DqaRbv=5e)Vj)yLn={u&Ol zPfO{wXD+0Iv9K!5kQMtrCm859656f2lPYGLJsx@&P6>{IJtf}XFVqEJHmTwJH{Ppz z51>Ekc{OrOj>WP=%G~u6V&1%M1f(*hyyHgY$;r1RP|lk#CK#o#x#EN|zp2eeM#?g(KjflfU7 zKN_l*(HqYLnj-z~@m}%Uc=p}Jy+9g0m5!h9Gq8Jb&K)1tF(Bma)|gFKjDXsFLi5+^ z`6(F}nYyuS0kvzkg&E9_C-*$H+gvCvW#fe=|DPqzjg7!N(z1eUJd1$C%rFN?ke@`E zC4=?4)@p8@r}scFYmHN=yeYxOu87Dcr_=IJw&Vw~ob^%!LVxK-u-=!Pe$@MXKu5fd zz>z>Ct*}>&I;bc1JGme;yxQZ%RpZs+zggbpkJMBkRImnFB^TwR747Flfh0XFb5E)vA5B&*xmS z3?IN?oSv*WQLVs`n-_$_BhW3@6$-gKE05*$OpU7gNX|ui-!0tAL@p1a{W3sy>0Me5 zK+PL@!(o}{B8qm`y$X_XrXp19>gQ*;JQiUB6 ze!t>^ce*Hym8m!j{c6&C+2G){*ee=K%%}USGkgj|GdANqOB)~I(Ev&8uEy2MN>Nu0?JY;UbDH31^DC zNokmEWzm6a6vz(F(t1Y0UKn>oPsCs+4EIH`tczZeYk@!&!7zUtK|LKCNhUz87Jfdj z%DT4uI^2Ct<&)qEip_bXX7}NfuhNdmtQUBx_8B~Rka*6a$^L7LB%n*@sQYegw&Jov z-3hA4AMCaZjxZsHV8h_v@qN3m0bfoU0-v>gNR?fxG|s2el?4zRcKM$Chf* zkJE$ICN?$a8zkGmsrv#8k2Ueu-OlT|pgufXhv$vHY&AEQ$2T9L`>6Btf)=BPp2@zi zhvI}ol|X|ll+m~9VwNpsqwKyq8WMB2HX2~joN2q+Zp$M~;}uyCe1=sKUudgbarJ}H zFQv7IdHF&15EFabA0J$cEM3W z{SeU6Esh^7$Y(FnMj%L%=HGy*?PE>PDNJpIH&RUB@9CpN>K{&G5<;9B%r^R@QagNm8A8n8 zB0Mb{hk@`Cu_7kQBB;H>kxwX4>@Nt)lvda=&&IC1UKeglMc_@Hh9<9lDV8m}J2g1n z+wLm7H7I8@5tn1JD%GfwzWqYBye9+-Dp3sP0A=`IY;sgskmwberBFSRR?LW&x;X=n zMg~7%eK|rTfA~F0l6G}OmQV%DjAT_l2z&DLk4RT`Bv}6~fR%Ai{ea{2iHH}Ojp3b| zt$-Y*tzriD&&=2u9yF;?#07_^bn=*oQ&8DgYn+Tm`&MV;;}ECUmv>_c+`0tOrMPN zSZUfO6Ox*71e7;5#!nGLZT{S(cVn_qm z5C@_a7>yj{&Ki3kNmUg|gQ&W=O}P#{fKOUD5Mu_){HfjdOoeQJi-2*f8Uj4@jFF)m z&%DiS&ik?@NNC8zQ2(I(rKS)6~t(7I%VQ*feLBcG}(*d{CMV6F2<{AiYzy zeaSr;fYe1#K*&^SOnrreP5pb8lvE~P1As`mlBpH?Y0_EHV^Y!UgnY(-XiYC)5eB~_ z_6LivKKN|%r+WrE(f0hoIpJ{cTgrtAoO*Angf zC;JcRU_9#vT+%n(Cx$e1=#)m>VzxhX9ZF-sFEq9t$(9>qA#Np;QbTCfI27Y->tc>H zCPmPPY3`{9B=#U`#kf>o`J-KcDbFA=&r{QU&I)yWofGA%2t0pW=X& zkEgOe#bqgRqdkIz1}RX(o~I4TLVlWpV6hL93=*=PMy*`1Y>#x5%R@VyZme>&yQo<2 zd%{#Tg^7CD=bdG2Be-jS6{JufBzS-U#^W6<`oXrc7@M|+4y$kuamrjxKleFp{M0lc zq7ejimhY|f-i-08JV0JrrcWGJWa0(e$(xDs(?KXEKR-xWH_tQvoneoyo%K@+w)0w2 zm;DfXK-eUX+4Tb?)@;{0Wx0M$`}}QD8Wtvl(89a%>d&c!lPrWRQh7W|3)QcD=Sy6_ zZBnhxt|9qZQsg%ZJbz!FJvP%q049AZs34;9JjEkz$$JDkXxj1dfpssw)*K+AsSAB& zkp4v;G?m@26XKzmnG7G%aADVL%VpK9UgU)Cn&MW3+Cq`z7n?0|W=|XFrzWhP-k3A06>c``@uCZa)?r zF;J#Sajl2P`?J8EHICFQJ~Wo`zGnS35l`(5*dj|s&~BetS8&P`{%L3ftPOCw*iaL9;9m84<9ZHd)zsPqdsi8>2DkCh8e zdx)jDdfDR;jEU%H6_zW3Njms1Zz3IDXX@3#KZR|p`Ro*`9(Xjp87 zGQ+hO?3Tm3Lybyh86BST6jC%k!j#ddrK%{l`kZ|uNqx)#ZF%-^=iL*hqMF9oeF9cQ z(y>8K?BV{q&9PjA+^}eW#|azTzuGWuOK*)u#&(BX6u&&)uz#TB;2S0(pNXU)Q;MLicA|_}Cg`2)Zz4->fgrq`#^3U8} z%nTiS`eo7B{;)+pE2Kj~olCEpU3@iCU~J>o9js~x-y>|SJk1ks*+e5MfMNJ|8HqXo z=RXUfFXW4Y`v8)==I{F~f7Q)=JXsG5Og|PcRf={@RR8n1ve>XYjW12VWBHL-Q^h5V)GPzFOj1P`|2D;^+U-!Oh7m?wtnk^5^ zEJu+IJWcogr#9$TP{FZ6rsUF&KF4oQ_Ld<>+48^3yo`Eb8CdrBF*EeKxOLJPeI8nP zI!DCycSBt@YV=B)0Vax?ft(I=ssE;oZ@O4<+Nwj^D7W4IU-#EM)rgPQ^J{I*Li_qG zNEtV69ObI)C0iJl9z{R?L(>y^AXW2sCE$Qas*M{zXjjV*e8=7=iQ#@k|Mc(u%ENw6 z!YyAKLk68{D~%ICJI~>B(<5~=D&_ijTM8RQB72mu&tv z;LCwByYhDIO|$j@tedwrdVWcQr85-g&Az*(d$V*Ke!TzmS<}}En{2aIF>dS^e|L0T z&f-?O9-0!Z|MwOD_3!n-gZ96#(M-lSdFI4&YFpE1|94ZEp_=ExdfUT#-fgqHY_|Uz zKPVr$@Q<(1|BO#S=sT)6aiNgFe^d8QW&bK2cQK{TdQTy_Dqi`YXUnju(KCPMl-oB& z<c+H_O~@IWtibvg@~OQy@PZip4PUMFf8Q0nLzrb(dt<96Vy z+JoAlsyH-0%ef?s&XfYqRMUpxY0B$`=aeLGnzYT z_q2>_Q2T3cUJ;yX)lU`*y2DTuIQO#Y_!H12TS}|l@PY0qnoKP^=$ovY)s>x*rq%=+ zG$OmI&(<;XRnaSSw$hWX6n?YHS3qbB#vb4K$;sQBbo1kU2Tj8-E{Lt9Iisy>k*IN5 z6`MU|SkJ6~cKN!}#ofU6r8|?JIxx()fSnSJJYQ}^3QPLf3T^n0S5~oR#g$a_FPTpi zi`n>TLtAP9jw&}=17uz~h46yBhb?C8z_Y}-^sQG_ihsU%GFZ^Udg8|*C~n@qVmAij zd@I1#+U4{F%E?X@z;XyV(MN>8!!kPNwqoG3T{Ld<(&GJ zb_4zRZ;E1>trNbuDdE44Lt$m$+H9mT<3B#0;}6z?KFNPL!glN{&iwDe{?8DXwEWjR z`S&lV9>~ByV|)w#0$TUaV#EynNoALrHC(jd`&9M*KgQ(Wg)UO?|CyNo9`l-XRsVmp z;ybXQ{kzm(2Zhly|FZ7?{^gh-==y*E@2`WL|7+_3*ze!X`upI28~49G_uq$v&;O3O z|32fO|KGE92aWe_F|!A1`1Z6-s>WS7ePrh{YA1sI$=&cz>|Y2zg|6(0i1jJQ^1?LVyqKXZhd$NE2lLlgRqNPWPIC|6?n^zt4+C2EX~QEB-p74;lT> zUH$$#x5fwhYis}ixby?HyzF0_RsP4hm`smdhd>hv2cGFjv^fb!9Nf=Rby<CoBf?} z-8tr0@gb`mAA=(?aXQu*9iQj}?%^UAFlIL=zmHrZt~%_>nvF&8?{j-uNdcSknCtWX zKHs^CKAH9_ksuj{nrRdL)*W%Lq&e%%WD#=RbI^DF1i67hUIRVU>u2KEd*i!_wvD#; z7@Dxd!~FW=qL^LhSFq^S;1|F0)VYoNXiSV8;)lAMY%aUM_HVkrx;Y3N=kU?0`W(>V zae1A^(7eBr+CirCHr`%Xck!k{1<^DxVau{C5OL;HinydRrl}2)TA&!#;aI*a9r&Wo z6BlFoN{d9YVpSylW>n4YXM35QUDIgkr>h^OYP)SyQuasgK9+tH`#qBo?>o2M`NFR0 zl;1Bx+wG59Tg_pjvCf}q;;PE0;uj%k`=3gZb#FFG$+x5u*l;l_Q6U2RW9US?sSc!RXVXS%vX+d*D5)8d)sbWUO8OPKEn)7Q&Z`nYc)$u7uK1m zg-@Qbp*y{Q+`oO#PO=+}m(gWW(-KFgvn2~ub75aoxTVxB1tjOq_Wkrt3IABz7^aVa zurcr-XwYG7h2f6mC-R#Q1F1vX~4vxt!7kI>frXqf%j#wkBDa&P9K* zmaeF}KSdXI=UpT8aQMAjRojF*&SP!r3WhFaeVh-$!GK9BP>`=} zL^cZTnLirEJ+*z5Fu@L~SoJ_qWAp};m5(r{(Q4>=UR3DFH3Z=pncu8;0gX|YO~bv) zc7Stll@?~c)O%Dlp!aC{8@UOB9%v_6^n`;Mj}%-tUtWf4%KwTC17waE)WX0x9>CA+)~|BXis=5L zOS)*C11lT zYW2D%KLyx*^h0nVJyFHBxPkqnSC2}A=eSTP)@r(e+ag2_e_kBP;eP+3addvIjD!6_ zukov)zpEpO^YPr_lPX(ra=nQA+$IIgZW4>z z-k^ye(j(fNUzCDT@u#`v=A{S^+WE6?b$>^4r1Mm^r*Bv;ed(8zvaUYE@SR+ho*gGS z6swz!sme3ttCc5!&6jeh7o9kBGuzpO^Ufj0T7()qb4&AzV0QegqO4Nb{Eq}Zi1j|{ ztKps0k37-#vGyNY;F@;)-KW@pq@;{hK_)l`-l+@A#N8DxBGey`S#sE$4c$o5!TsdF zB6c4G)yeFr*<)MGsAv7U`mqTORSlB{&Ib_gF^xGeOTPaf#@;h3%B<@aEuh7gq%8sh zBA7q{L4+bFMNCixiwp%w&N*jPR6qnof<;Dh&QX$p!~z70oO4EU5F(tM;>>y;qoPuDQG)kQ448H8H~2R5I{A<=u~Jigm^UPF9WZS76q1q`o@wC*kj9 zy;mM;{5XR_MH<0@PE_kaouLZ8Y*-LLMt+{z^z{36tS?bQgw^T`gEK_&l;-pWUUd1s zmk|u?tbdWTY`Jd@x}Ka+`g5Opv$LKz@k$Ib7r-v$)p*6;AMI?PH~&E?p?9aC_+#9{ zLA1mR-Khr+cimRg53zhD>2V@)@c-UtHR3g%sMK~@Qw!6obzAorsdtMxi+VRIBM>1H z6+DNwK|T80hn`NT<1CA{sl2u6=|r|PlI`DfN>(wrT30*$X?C97v#tdiCX>>%tK;nt zrMR3*Fff7oLss7vNlvjpZUW?R?yvJ_pc9*Vl>?=}YN_9dAU5%VAi&3sl_3!PXp5KA z)CqsNt9Q*$A|Gr(GtT7qyI<+HMssu7Q6xS^=d;>gSUzmN&N%uo?3UgeSQ&zE^tW30 z+w`G)T9P&EV=)Wq#D{^8zY=aybQD8ye9aG3w#g4ET9e*5ULHicr6|77F}~|`A%Q#_ zm8e4$0b6a701F7Av7$fub?!K-G%`N)8wpE9k`Zyvu-gMX3M+Zyfl`<~Ym0O)W~XSJ zc$Kz=jtf3I5CffO7J(n<(@e|#gDmXN?P~gz2iUWWW^FjXW^!3yfrc2Ci1{{iyoG{o z<2PR_(Gx`XJqVS24B{84kf%nOdmM$-wQ;TDXLYPEoL`>xpAkx5;0aeFn`ZA~Y*BKC+PAyQ8H4H|cAB!?sJ>I!F_HDo2D7b=*WNNF>E9BmBpuBz=u|pntc_QXoH$3SktL_qT*2G8G9eDGK}5)B z^IVY66%}Mr5^Hjf?`sRb?tR1TpnJ)!TjM zPd53~f8U7UU-f1TNE`o)53!`fJ-+WS&L{*1>rfg!&=Tys>OxsR-j%h`T!OO?I}x{= z8T~q3qi2d@eC7N0H};t`>tkHvOSIT?9^}Yd-Gsz#ZH_)`qCQuuW-#yn`7_J)Ujp(U zt6mAdGFnUsmg9k-agbmg%?ryJd+Na%9na?e0e`h(d&4*Hyu$H7uwDk8ynhNp!ChN) zSEV!bE6goqp5Wb_(41V|(yvZ&W4hJ;ns@Gpe{dKhiOJte#!p45+f$t+VRf?^JM;)- zaGG}9bIvmgkx~C8RRHq^zPM63jnU&vYF_MGkcidl+;}ZH!ubFF(pygm(NJecFfU5+ zh5)Hg-zU6HSd&EUpH<_Z|ELo2CA$zlU6qhcB+$0HO|!>fv&UG7&id>SE)=34s zpOlVZ^xTi38dCIJ_3V#biQFwmA~R-rbgX}hnLmqRT8)47^c?n??MQ9 zmqMVm8+91W_UXDgh6oXkyC_4Hu$L(y8Umk-NVq1GE%d)1WNBJo?>Br7!r)_C;|qji zQ`dia`>@B9S2z}ZhO4b!jE&2beH_v}^CEhHx$IOQ{s{Uz`;x$%qczXJ+ z;fQpeAsn=H>B6|op(NH;!IcF>-6#IPsAY(JuF$~V3NmIeyfP1C45K44Y3V`!l~(zv zzRexca)ofdmILC`{$u~)4=os3!doSE=#?wmW9Q%)i{6iFj=q=3T-{t9uc~t#Y8p#b zc4QY=6PNvz*z8AOnnvj7+ZfKAtIWJj&-L#3ynVRIcj52k*ngi0At{xp@mjS36~V}s ztDSplZ>NuVdR0EFxwf&08-KH>skKq9ozDqD89I$Ay!yIP4Xaj!Z)RKJVTmAad2_D* z|F^jRo;5#+BWC$i@cpV_**BW~apCjUcb`nJ;54VEo|SesgdH4HGs}CdvAV~EGw zKkoGQlGUc@i`$wB2q06ne;UE)?&$9Tqc)hX4>cRK1+@mR=-_E z{Ci>pZyk#*>9HZ-9Yw}pl=m$POv?ac;zp6Vkm7%Mx8Rb=;Q!%ziox|9-$BYx=TtS0 zI1l|69nv4|B<$7i(ZjV=OV zwrIinS6bC3+h_!7w}QS|VR_V@h{8f@MCS)zfAoMkG$QqWX8w1@c03ZfxHpqZyP9U@ zi1%s8_zN+(4HCrk($N0p#f8~)gec*D+R&bJ=Wi86uvEEdh()}pL-=5udu7ks5?&92 zukVTwj{qw-Dd8`bHnzQPmJ&;@P2UkCMReYhkSB!m|KFdGD%g0wd)Xz#ow$|T(M#s7 z+Ri1htR3cMr$1gG7FbhR-GdBygQ{7nZ@*8OO_vmh2uFP`edvc8VZnV@d_2+xlCc-G z!bSz7wIWB$Pn(r%xFpwfq_lC@#>_d<**VW}iAV8GE0I@dTFQPGvlj4;b(wEfg0Z1I zOZ|U`)Vvaf-S5-l=ZMIXAcadKlhxcR%OOI9DCttn`V*sU3lG$?Fe*4!d8EX#Muce9 zH_P0=bsDwIgOFTy!E!~ss6C^ACfw!!{IR=CSik|1VI?IFRXh4;c&mhDS)TKZ&)Z9M z=8VUMht>bHqsgPfn+vUGp0mve1~VEm{jAt=t)zTF)T(g(c^J@(B@h)(f1Rymem$0v;hdl|ya)@Kubg9Ksr5#mG^@C2P= zV964$-dv=ip;=$>^Yf#ndWZR^;@h{;=g*&K7Z&zBObj{m`pxRm^@3kr0%*yTfV6-w z5F@E5DCh<;Ani7lg73t!=dwpE2Mt)A1ZH@ke2#W2l~((r?a*`S0kHu^DJdyjsiee2 z`ZX$x*|Yt!<|_K*z@Wge(?BRS+zsCC&? zvPJK{eR^~H>V9YAB~2Q6WoJqFlB;GFb%zVPmlvPRr9oKnWe{qVqsNOp=@Ost1oOO1 z7Rrq1)c=%A`~*}9y&a?bY?DeN7 zlGo_dbsP|BdQT>5JafGNoc1S5oh3w6<_73%haf0gYTC=HSz;D$Ia0c|^MDC4DLXD* z(itl<;zLA|Z9b686_=Twy?-Ze9b}?aa0hZ$}Z!zJd%I4shBK^uC3%Bp~;g)u8K z?sAg8fVxPEh~a#+Z0E<|n^(0i%*baFVoscURUZF$;y%W8EL`jY|N~_ zS2htj7y%KXRL0LWNRQLKRb{Q|ra=wqxQyXN4PX+0vO&RnYsXugZqlqWUFplDope2 zQIkGRtg@CDP?krF&ylfI{3FlX)YBLY)(AP%^b~nG?$C>}(g@bGD0IO_72OtC`}y@< zH9Xq-G**u@uXeNzU38*WlS>GLHo2Iy{Po&C-{~*4)Qn_axr_12UaCJYwi8kfI=p+( z?NQZi3d?e})x%vI3BDykYPKhgUhT_&3!QmYcOr!MUr-9DukLm>@(CS4yPY%hpAO!x za?@45GYi%@B1tEuEafq$t=U$ButbH&Bir7rHP)@Gy@vw_n==?uPv9b0z~)l4we|{P zar6v6Z(qOFMI87(nz5dv3Fr4Xu&J7h&>a0smqx7c*|O5#KP!ch`5EcIL5z1iM=l9L zobbSl_lh@1R(fG)tZ*zBg|Tpy2vgu!MFCTc^c*n3yRW*={sT9Q233z>$S!PSH}5a^ zN5u*^aaHfUh>T|6=(rpx&$MjX-UQ;TqruRRzy-evrr4C`XO#5!f1DNNaMW$~MYv$) z6)=#E{?ua|?$DH`WhLuHk#H?DAcL0C{`D2$+A*txMJNhLOM1*-4MN!+DB9Bo=F>^D z%IvDM-4|j-sk4e9_bP9vseF?~-Z@mB@dVxDN=JuQD+LnC8jhR*^}GnwTAuf&0KeTVP1^ZUvxjs8JE^UgAXk$U zo+Dh9mNSNm@g?jOLprBB*P;bYKe=Zgsg%ho#Vd@du6%hC#1M|3Geooq2vc*sS04V* zDlm-MA6+Tc?PtB3xt_K5X!wGv=L-P@-(NpnMDcY6=8v@uymte2G}7c^wJfqtMU^P} z^9zA;I|5p*)5{fbFQnmK^cA$Zq*H2iSaD+v2drW&3);xC1Ot=&;0ZN9ToEe#amEzU zVwg)mNLLKv1#6`^-kix_TpWRT81;qi@1qbk?YT+ThG2-fsMA6!y+0x)GG0I>qN1MB zrMe=RPF%>EXc~YeYtzzzK`xTHcXrR@*n=ExxpmF%cxg*tsmjPV71t83;W3crAsm0M zYa}KL4y-B{vI?s{dYh_R#OTw2JEG6=98-R6$5pj@pYR(tnH>uZuh^6=ikU4v+J85n zS3gvb@xK%2w9=1*{_ZEiM2J}-g6E@(wIKaC{GkW$^kuE=(=Gd))f8`D@)T`UJdQz# z286n=u30Jf){!P$d;g`2sWa&Agc&-EA8S3p%Hh7s%&DSnM*DYiP{vaFdr^l)g_WKZ z^0N*I1_~s}`P@Ov{m*%&Nrg9@rZ>F{2lw)D-~EhfNwl z^US#GB@)!V^lB^@@}aYJS=G>+id0qhxb;zTutvrp5w<% zhFmW7d?9K$zfgWscyck?_lc61`K_7+_2d?hqEi!P@^OBd7YbB<4|5ZyPND*^oWo_- z)|e`%lqq7#@m|Ws02kTnRF5JKuk=~QyCxPjhRhmG?X}{rtw|c8Qqt3}UJ;X%zNYnm z^n&&QAHL9b=0|_6w>PT>vB?EF0g}j^;v_XcW68mN!iw+B*EPD(e1%)WFky zrsG&pu72QZ$8J`58T?F^NPYypWDWX+KgnpahlamcuPjUA;MKJvRc~|UELv7E>RzEJ zXV%m*rM|lUuTRc{&t{^uKy1YgP>7aTKW4JM|C!eyW-f5PFg)SwTgxFoK_%> z{XM!FQ!E`w+h1m_vxSDaOAsDQGNq4GXqlyvHJth!>M3S_-QXI{jueY4MMaf^{u(s5!waO_7o48f?WrCQW1r(JT^A zhi&}2SPi;3@=Z;W-NY%)Im?^g`HS_{Y250Ig#`@#HAl;EKMI;xVM;;?IMoyjGL@hQ z%z4*@`0|NbjQw$Ss#Qjl39;FQKkR#K8dt+7)bQ9_MrYW#)m_9$tDM#fK~}nRE}hVQ z=WdFsO;gR&2GoZom;j(pjy@lvSUo4rng2&K%WaDp_orP#vmh}>vAXz4S~*2o?8N&^ zt)@45>55*ci>%V`zQj=weO}R!+!_9S=FAwT4 zb`{ozsbqKtjZzC!7zZ-->}*KnB?-;O=WC~*4EwW|C!KM1+Bqy*x;1w>a<-QKjC_|k z5g=dMvX%C$h7g7j^Kfu**z-67i)vjqyS!X*>5^`fI;Jqemr}Ck`51-Aa~6}+Pd8LH z7bnygH{wbZiuA}2akN}oCItC<%Z>5s^@fPMn)BE;fHLiNQEAcP)tZlzF7iT%d^v zqv2Qjr66kAm;TQgLxk%S#k(L!)4J?4;qjBb$oMqU&)KrjFiT|!`cKUVD6)wQr!&S? z^0e2eR$kBqAjP8og=lua3{d=*%rw$ftYM`1vXSCl(t`CXwH83dHqAF0fpC3 z5X{`?=^#RE(JB9p5PZVUsT-6ty3VC;N|LpgY(@H!yb*n%AZWlS?$WER>D<9@F=7>p z=2&=yGJpQvky@CERh_uyNh{V@KjPb_?8dn#-%E|>uDfsRoJQUq^6dNlvyB(~lTR@T z5*Myq)@ZK8!7P_dQUOE@8#_bf;VUQCCa0ocZVz^i7=^d*MIriUAbk|wL~O~0XLT4^keaFJ@o05|dhCF%~1JKE@-r;b*mUCa4*XG_; z&)1t#6|8C&)|Yk*QB~}mvl82OetOAVsC#)vw%gF=c=|q#VKPC@6~ys0gRhFc_<@d%WfHXU zaUo;LOV?&BOh-Y87pb!TbcEtcLFU0R$(~&DI~=8BNyEf?!7GKy4}uvrub`>4@iBy& zh0T(-O2)=*#y3L$*7*yHB;XJ0)P;-aU3A)vbdef=>G5b@HM6w#b06xLyB=U)l}kUiKA#qG@ZQaUz$~h&qF7j zH(%gt12RCH+?GdKI>?~cS6UzqhR-aI$GeoDMN}+z7)>H`DFxMe^8;fyJ$6EB>&Al| z%7Ues#`*)_|EVIsH2zl{MD>H9b7cu5oMeO)F`G(s{RAWqA?Z1T_2t+b6f@+=zO7<9 z2YYG1`}QJv)Ao$j-h=WirfokAujci+X3(B-IJAWn87-HsB$sV8IacLNIM0kja-=G+ zqnD}4rI=RNo}M3BvR*a2koZPL0GWj}>uUkY6_qOJqQ!^_41I~@V#ylM;^mpXSgF^2H)%sJb4o7xB|xz)GL$8;QM z%GQ5k{eS9%#RVN&gdq8ZHRx&QZE4l z$^Cg<`T9Ci6N*PakrzlLbJ1E#UpGD%I`Y@($+Q_wzE`ef?Adr*ITB+dAToWvs-4E4 zIpbj=gS$6mO%<(m0Vx?+UuBk`#{g8wY?4T7A9(TFbYy;NMAJkYK1+|qQS296lc40Z zRf;yqn%EgQ9xvN9fkxTbH4Io;Ro(XsDIBV~4z725Z^1cN&;fg&`?QB*^{r5&{Q}Zg zZ@{GBPWNwvuhI`G2cj!ehjYxzGv`t@b@GNAk9Jou=zB;l#Nng?uZ_Q$LZRrKfv$B_ zgPb>YH2wNZoxesZo5N!Lp{J%5dVb|rV7}HcFHci9ytBbl%aQkKh^>daMw6dz=4h@L zV)@S>-{2P*#m&DHuBbP_b7#3ma`0VguF?v%(oan#@dj;{+zEhe;#;Di6kTXaN~Iod zZgR}o_cNzxVvW1-sc6Odq5ZVn-L;W{_3{}i0nByGHFYIf?NW;`YhzWBM&t+~d0btk zsIB=B10$y3?$YyQpTJl}=WTVg??Bb-igb`BYpK#obP+aa2`Tn7;Ep4pC9k9sFX&Jp zgziQg>Hf)RHxnTVnm#m@*Su)4pz)gg)1^<;+^R0YDM^qrk$7)5(On87#B$aKW~6)l zL+0i`M5fmF{L7FHP(Z2<{4w5#L?)+XO_{Mrn_YxnlrLE_>4(G!yDy!M8qFU^#%^(- z`AM?=QM27UQBZZ~9>q}|kYPkoAAI~HIY5)ZiGz!)(}guPs!F$>Sj2AjJ;jHC`T6&yv$FERi+N6%_plk8-e4e0tqwB)ETt$uoa`w~szUBWPuqN$7Da=1Lh7*gs^mx_m%q-f;JtS96M zn6G3ttnr{Khkk7>$HnX0jn;mOjG7+plD|;eGEXSYnY|9K(Lv~Y2G1z0!c=hfd8B4l z=GO^xQWea#OTqEcujmFDr^c;@-RfffJ^$GGEt5kzxA<8YtKm1TDLf;5twFrUmcIxy ziH}x92ECx|cK+KS(`_5RQ@7t1LMb|{fj(Va3ANElo2XIo-77=^Jbx!2UBLmbcp-yUI_ z=IOLue3$AFcG{^%x*McBJqns-ou^=0Rn@52Dzp*@0SQ%$zHj(kC6c3$BD`!>>oK&Uu!E`rUgB^uBHBBd~oHst82Syj}+|CF5 zr5yX3m4uh+vLx3w*4JCBuw(dfaFvex=)JR9#b0SlheFp0HaM^rUsm~SdM+UJ#uFpQ ziiebS%^#VHpnyI8@v7K!N;*lo_C5*^yY2i5aY{!!9>=oU(7-fbbmX9%cihcrj?YD@ zU$5!6J=1ZWqbWm%nXVv0CcyUlFxw8wS!?je0fAIrq~OK5O8dvoa~3*@?w2=Ybzfm zPH$CRW9o1$)jxgOQ~1z%zqZK1L;Bl z)(||#x@-sIeE$9JHlxRGLq>njtbgE)Nv2oZ^#eS+DtxcnZMV&h`e&llJFV&mk(?&l zBq8megWq~8Mk{We80k6#@o4*3$5VODt0wk#+cp8bJeb@leoO3#R-y;8caAJi`xqpV zD=2AqE+ZsQXOgK9g|kSKtY1G(vyKZNf33-{W|Hq&@Dr}ODXxCQX1WhaBMxvw zJRbf-7@CdsVYt>CP2ML-R4kDxaMS9`+ZM`#8)7X~%G1`i=_(n!ugPdX?Vlq zi4Qx)__R`^OE)v7+|UCcRY$s?>TmWXHTRBy3hpJeTBWh6_fSy4%E3H$Htrphk2M9z zOEJk)*+0D~Uw!mlnhrBdI;&+m%_Cw_y~AvI<Eel7Jd3aUaW6+XWyVxYOU3EyC2yqzP zD-3)|JzkrI%o~uUk5MkVBvoJU8%AnaQ1aR1EJAMvnX`MOM``tDn46JmLrEi=;+y#Z z7d-PUAF5!7&6Lz>MR=wFZM&0WBd__71>{bj#(eg;RCfqI4+MgZX zS?z}W_c9dFtA+W}=_Gt2nerkZ31B^w9?vaEYanhe)2QgdJl!ibdIM0802r(&?wFmVn47u91AQeHD#Pv- zzrfC{fpnvtqb^~=g-W!0BtWTcCzScO2MVobI0JE_uo33W}FP?DW;>_<-~6D#XC?n7?Y+cuoLa{(_K z-c~SJf$DqVuvTK+D@dP)(DGu2(1ebJ+8Vt$RWyN620>zosZSa^V$+oKFW z3zhUN9Hp}(^*}?Ku~uAbV0@((kK?kEYF*2oogdaG-s{M8;F2s@XF|fvp|{M#)X_Sv zAOazfsR%J@)Ud?n(`%s(JHOOtqn`U?$YHH1Om24yvI)&aAXQI#WR6p0%QR;`a9LR$ z_$y#7$A)|4vPoRw&xfDSJFgD@#E1jV;M|WM_;^KVTIC*yIxOV=6V+}9K?CWuLuUWK zhkwd?`mGQrWvOk+$SY1RRX~96XSLG=#0OMVswI-sn%!2hzlWI3Y&4oY4L{c6&9BN% z#WkGf!*dlKn)06yoKDl5foV|I0^=_FLGV(vzYU^D#T^ze;OY?Fbqe7KAHGjW67v$R zj)kJGhfJ0#srDfd)NA>zV#RSFJvBBhgsqqgDc7?5pEN=!X?1A9NLZ%M_&T0&vBGYx!V+m~>1 z2p6UrbLd;Q^*0Jfp+30QDYD^EGHL-hjA<`p|>eq+7yIWlDKUA6qW*ic(mQkF<%Yf>)% zg{*PI5}HJQtLgRwJ0Jv_*LsQXP*3=>|&qr|D zmnCv|cySazKmIY%2bO-YP*+pnPC1M8r(6R~zmG8dZwNGY%n8#Y&6cqj-BFm{OERgs zL@j3_OBcJvVeu4if??*A+pZso^$%}% zNpwATN4P!+^X1MSwcd~9DoZV}%i4+^4XEI8v5P$!mMmcWb>W#kYwCfJ&7@>BkN(>d zt1;oM_GV#{k#DX=BGkRN$l+eJI06`X!oJ@$V_w&Uw!GSZ@7s7F?<(48H=oYZkYZ;2%mh5UL|LWeV@=#wI;ki|YyR&j8={cQ)yV{fA?g za>-dt#l!-N*Oi}rHzx%PT|Nf768%uW$|h@l-lq!X;V!ZLn6=T0u*Z-u(43rGv7zKXafjIlRPG~doCasQ)Lv>b0fR>Y>0K=be4yM-r?h_#NX!oQF;rs zf6nZwB&X$&728o>SR!jR`)xT|UQngQqu2QNzB!6qu6aajvyCHf5PH*x2+birZV%Qj zm}yrf=~bb)Pueh_$$uoNTjMTZBFO(D&izM?vfhlM(?B!3F+~1A~71eWAl4UkcbHp;>EK;A#LJb-O z!dL|q@Ag9c5Vva!MZ#PT)|DAkQqqu>Z$^_xi% znrX?jo!C;&>Z%;NVRw1~Xs+v|!k1`h467^jAVRUapKqrOyNpm`N3hhl8cjCr>B0~K zIx(}e9)5dX$$mPhsdZtf%~1-hNZ6& zDrdW|1}HpGhepvFrTvEOj3l@<9K@orP5l&#;-fX5$Gwqr_bsnKkEu*;Z`QEvG894_ zCN$C&*MuLh`Suv*`ddcUhE`Lrzl`@2E?0}T68xC%m6cIL;Pk>Mi;ffFk#-tTU;TDh zqqrqD)YOCuF?-*V*@{}k>U^A8=xmg}7V1Wn#S_=J6Htves-VVd)m%|bD~8ggM%O%j~ej0QZi z^_lGt^t`JOxOciO4-*jYYhNdfnA%#9Nt zo4n&P#wX{RPmXr&Bj+?AZY-c5%;F|EaZm{s0z$=wLmr=R3iIT{lWk&FIiC6)&LyO% zf>B4aSWiJI+=Ja$fD2TH?~#yF*lZ$uzeHi*m9(91uhZ>xqCy$3P5ElT6ciBcf$sl~`^B&&kH zbxC4>eQag=4#Ccmqo#j46fLEguPz7V?761D=D4}>H=pe$UvGVkfT<@;6Xhoaau+)1 z?fk^&QzEs6Ju6<}2vpJZ^I)48+XO1t=s8~HtWlOBzq+af(VD$LZx4fa)dLy5v?|U> z9P4#F9SHm{jrT>>gE2moG}gPp?lgRIt9^8C#`yY)rD=Xp)|inyYk!5kaTSD{W5Gk( zxd(8ZRzc3#miiU`3dO%y)$(ie8n;YMKDl1X-opnH(ad}N=Z!z6`I9t%PBXHvcxbE# zgX--`nrlvv%n897?s5=d8JZN^_us+mL6$iBvxgu(|z%(V76URLtw5uuq?C+Jl?U4f*DAXh+sc*jO0k^LJy>tY%w(rSQ7_nUmG8 z3dX7fxYVByD+;i89X8d>*BsTkddZBCxv_$_-!T!#JH7H4`}}bP%YgFHZ~j8Ps_5d} z)RM9rVyiZy*yU+keq}_gDFOA1^*4QJ!yox|d)`73rMsJo{Fx-9Q?+S=LVM0V(=C8!00gSvy!^F}(?eeVf3dU|_^%E}|aTp4^(E9Vj&fShRo2uk)h4LF4LS3leW=EPW= z94eAP97H6DXB7Kj)C=$dU)fewr6UK+)U9wfy8-BEURK#CUBa06qry=LT!7U_X-$Y+ zxE(^1ROcQHT=;ELud7LKGRLK5pxK~|bAkchRtZv27vPoCP(4v$-X;{jAzB%ur#IGv zr|WFQK51V)+Cl*s6zF+*+0Ly+ivJQLoKtg&!&dpxx&m2%TeT9)K*)PQ+xx^(b=%M$ ztN{%$JorqA0*82XT29pj2dNIp3pI>46sPo|)|2}yk+sxCX2roGjou3<1yCZvQ~Lv=?m6g5U53d;R*cjP6N_Vxy# zn8smj^5UMZTr?YnF<2k?m{eVrVUiZM2Z2LZy+g%b-0S;z*r0CdxVlz5AgV$&JYkEZ0dsYF7V z`|QE^`6(g$c^imSLZrvfBPhkx_cQJas?B_V5-;w#3Pb0Mp}1qn>EVLD73VU24(Nts zOOMHsCJG3m5a!LD*}mNyHSYl_zl&Ph#QM7LE`tY9lcs7K1+1}N{BV|9^`JY)ByTDx zii|}konEaXQz9^yWvx%|}~1Nyr1{VtM@tra2I18;Iyr{{tSzBT=})R%cL|;X6{4nh|1a zfQLL)kYf&)Bm}I-Ex`Vik35>iLWoowRs2^S-l;k6v)ZNOvxcVLIU@ zl}rTZ%gM_K77i#uvAh&$H#@P@OBx5Z7r}rQS&{aW%@gFmO92z&g{1DKYEqsNg*Bnj zci)?GI>U-D%O&yI%8)E+e=`AtfoxmJzw!G!s`X33j^$}d#a@&M%3E>ElBjpK8or`t zed*wB+eB!%OA5Ck?Xs`!kB*GbRDWn4kk*DK1m;?8!O~#^xiv>-RZFSSP&{^zoEvvt zU~*QMV~#HOe_E?jgYu$aVLlk;O2RgwDO)Ol|A(=Vg*`%PsIIAm^`B=FcKDI(2!?xB zRWwIE*w?NHVvr%wM+{Ns&7aP2Bfsjt$}^JxvSFWJ^|m<2NA@^7R$w|mA~3ytumgR6 zWzUxY7c>Rc7w(g#ZV8e;xxZ|87JmX#sm%A&Ed^4>3M6jAV>{^BgFQW@n54zoObIPp z4U+&Y>U@a60nkW*0iWVhwHPj5`rwjz{W-yM1#sl%1BdfF&11e+wt{*}#MT$Pr$}OX z2;1kP04&BgCg>HYIGG)(>#b)zewUPVeLyYyoH$^wg(PRGY!nE^=9nZ8XSlcoPZ~Ls z3a>ma>-SI<=fYf#_S(bR8lZiQ8I*F@{_fcWzr3Y4hX)q*R_t=_UCmMlz|XPZkC8M@ zkMNgalBRyy7*1qk>o#pLqd+HvfRzPUfMRRVV%{mePS`w=x$po-h_I_fy4vPtWl5Sr z7+P@zI?3p9bqpVSfds_JqxU8-?MTp59PpJqBE%c2bBJ^zq*{7NjA>4_Gr5M}`JAZU z4H-_4_W{gJO-;GBQj}NBY&7$AeXE3W=3lbPAzF;tuj&B7iyw+7PzF*=)Q7-oowk^a z8FEr>QGD}Exp^;Q8Ti9{=8GzUj%e=@)r1khHpp5fd6AmL|-%9o?Irm{A z6Dy`Z`-&PR9pFFCLWNwojJH`o$n!#jeuMko;$qAi@6JH8iP%m*Y716AQI5RhP%sTw zSqLNXG}+Cs)Y>flb!Q_WmlQfQE+Kp>y>ouy=${j_4iN&?l^k(?;>)D8Tb z=cs0-DK+VjK(D9}7z>D#@U9_5=eG_oJwVJdS{J@yU42^lEW&K(1Yow&yCykNS=tA` z(h*65IbA8lj*(m#3*bEGBDR2$RKfa^m6W7KVBFwb&I6TRsmd$Lo9M`!r!ec@%eu#{ z@SAE7b>%lFkk>D75T;37z(p@GFee`w3cwyFh$~d@0^_#qBa=uf(_+Ho68+|n6*#Nm zUlG~#gI`D0f*Jrca4dO=d90E%4deOpOUwrYN1W&0 z&<09FKAD%t^J=weh&qsR#^y@o#Sa@#yck{+CA|E4gBVemkgS2CW@pV(i!m#F1binb zxGx9L4&5An-d}z03nbk3D-V^lPJcC=AA7_<_EAptGvMV(-}A7J;8mkbqq2>3QU2Xi zF=gwwqg^sz1uQ1On1{-1IZ1_j%aC8)W{1Le8#8z1;&o@A2&>^rAk(zm2p)v!zK5Sg zHF&_S_rW*}ik?4>F>H?*iV?COoju2;@$ddA;!2IX;&OcnEasziP?R2<(?_MsYSqVl z#18JvTjCcdYHM?V5kKzz$5XS&D9AmAQW~NFJxq}iPHP)GU=#7dEZc1YAfOGde>g~u z(9XMWMQ&SVKFb9@-@2>?XxruTsBOp~FGIaEXgZmnQ~)@rGm>X5b6gy$tMbVQT7 zeZt_YDJ=K6*RiEQI8L+bFGR)jAj7qhGUs>pOVb%xyuxo*}1kKr?kNU3|kO)mKa}7)sV15M8;LI_f}( zP!h6#OoH`;Unj&42{Ah`e8tYuM+ryUW{UK9fU5)Z*j7nj?#8u_EX?33x1Ol68i`*1 zF5C0npxgAV=RGTOeiP7q6mw)W^)D~6vU}onDmkL! zae*vHr~|dIGLO2Sev<%Gr)kn$g;q&MSJvxF0&hj<1g|p$lQU+g1>QNpktScJ`Ia-=>-*myIAptr)w&==&~9L!t>$NVRlMkl$h#&3bN0bdzb z?}0{sFNm=G%ghRqF3g?h8MBd7u2Zg;;s^y-?Jh^{7<{Tl6H+oQg`$JG1d#;V49x=s zi)#8LR3Ohfa|kJKN(m%Ja;n?OvF;`(4Q8l`xo#OyEg@!aBCn(mB{*-4-yw&-bVBCZ z>vlK&K zmc0S*lL3Ms?#+sZ`rB>sWm#ENZR>K1TW!sTs;C$=-vs8tFaIQP==MP7c?g88=a;Wg zI3Yr0K9iVH+dyHWS35rn{1O3vl`|_JmnNTUBaod45}NB9UoVp*T67@Llze+rU0~a7 zXEAh3pAWM&VqII(6i7FqUL-v&K;>2iFbqaf9UW+@M-sxJ)S^849Ep3esY1;LZb+5w ztUdk@*HMuJ@3HmiytTz_ODJCfQ4779U3cQ(89J>HKC3%#*;Bp1*RI>FYgy$zjo{{B zR=#!!I+F!e7KswU&!`EzaSiBWI_QrvnL`gt-UF+72r54-1_@HYbHXZ#>i?i9+p0-V zf6vN~5PC2%15_SaPK+p~)~kBtaWZI-BoDtbyMi^#sHXzEm*!6?*{Se*dA!=aZJP_P z03oOWp;Q&Cm75+1-_8V%jd3+0qdp*wa6i0~0MqUH)jyI|e@*#e_S;Et5O~)jzb9wt zkp+%RJxyLv!ySRKo)4%QePR-q>M{bD1JZ!*YD;~0r7~*uC(Z<=HBfmB9XFM=jA8DG z*@qu9{Z|5_Gq}{Qh`MwGfkAr#M|Cxi=m3=b^9kK0NRA>L0h*Qg3n`dB9%9xIT{EC6 z2H%z9IBqeeX!*c9FDtjQjWF*+9he1#ou}Rdf0woCliRD zW-ghtuX@2~AV+arp|EvzGZ>sT)`F#t=ijLk#GBDgoH^z4%D=l4N8eI1di_$HXX?o~ zNMz-2@?hOZq<39<5*QeWk+R8jsnl<#67e|rb>2=e@?igkqIwu>-%i50%Y0(+jFazL zr10NT*DPBZ8b32{jFfdDHGeBm)=wibCYqnNW`M`=vyqgAZ8~GQ9Cj?-DwDTB3F%)6 z+2j9C6m$o)?1QSJvawutP_g9DP zwOQlb!&v!VPII45bB|&bv4e;{)quz))(j4A<)YVAKfD%Y%#&P(0njSzwEs0(&lT6R z1CQuanWTjdGR?5l$-Ab8Gg!DGp(375DuU8uM4EjzPrV^Qs=%D_IMdQBQz%)48pum6 zu0qt?*g>nRHU27?e>Sv7nxKvL_TB1Ma*fwp2f{;_j3_=nC&r;8iihiop#s3YbO0T& zQ0Dof$`aAY^o&7;NK`qyQChUt6nu@4U9!v|z~QprsaV+V#Np?Z&eAB~&%wN)Ch#h? z(D_~lV`4r>QSs~&eP;rd4l~LwR^(zSb`ELedy(kdUYQ};zm;6sQT6aJ=_{f_nA1#; zVlH#4wx7L7`*g4DD;iQvfT2t-mo3aZ4{ z(a#ne_{b9)BAtFsKG|P7BfpfzdpgpDtE}K z_Nfp(0u=GmXqr4TEBuo4VFp#@M`nq*`w@5gmp`dtWF@w-clz^mHmDYRn0j1M=QYHf zfl61U)B8U!tt||#NR|1!oU4CirAztr1VqluCD_KK4UjiiP%B^937c0yvoWk%J84#V5*mUEq^ju1PSBHs-a8IOQp@~)_-IA$I zPB47%=Ou~+?fJ9-nGCEg&4e(pzt}W69ZpM(yROp7OMsAw^OqZAx@Yk&%JN>&?v?}S z4`K5@5~^_bZ?0f#oWS1{Q<`a{D5@$UhAQmSPEPl0%LD)o*wN2$DLa7KZ`TGcbtJ>` zZpCFL~v=) zLmLjDX$suwi&Ht>vX7+=ntJMApB)%7B&Csbpw@Wjz|Ia6+XqYsZr$QPv%l2)j%xOU)nWe^dv6(4)f;t>sz|8R5d@?;G*X8K38fnek(QDc zX(>?&Idq3~mw-|VC?awwX;4a#l#&oo5J9+W|K9uQz5mbm!yR{z;RhMYIs4iBdDdEU z&AH|T%-ufj1Vm$T)LB9QU-qw(XW`%?6C5lAhs%ItQ03E$Q|if11;rUX{H@0x2Q}Wq zZuxR|e&cId2Y`RsG;Yodj_T`W%aEp>&lGV?Jd%Xf9T4vjx^Jw{tMU6^%Q`V6ymbo; z3sTM(4Ika~of&hxhM;b5AmEy)UJ8t>}4Iu?Y;W(Uvb? zo~L{0wzMGhm)z6C+}D6u)_>*wb#P1n)c_9MIcfRNEO8)bE#8$IaD!>Jw<_iD!=9c| zBz&Z4u6131N9jWI7<+j{ziC=>;%PYx0 zgj~6UDt6=vy~0ta_EWf1lAw# zYTBHwj}9K6J~XYhRh#NbZ5FMPKY)4NTPItTH_l{j`&KMB^k<5{#j7<4dj?Sb6!evS zo82p`*~Du4vm{3um`ft}{suHSefbo0yvX#H>G?a-xpzUVkDa1AGDMyA7>CNARBBkg znf8dvq%uoOYyb6dXjZM}R|IE{o1{ZU31NrNqj$TbXYmMzC=q@~UCC70d!4%tPPaeB z={~wSyXR~ZMoYh5TXVCiX4guz#?j=Jp;BpvOZhu<$=Ww+AxJbRlPvq@1`V|OOkv2) ze()QA{?2auGD3^QTZ+Q;tAAxrX=m5UE&WDXTwj|J(v5~>~sH354IdAv(A9w=E6aLwCPgeABJ>g z#N9QlE6*rv#cbk5p{{ma!#xzrQC@rd4l`O$*0>b!D`GlOn~BFPRQYzRS&Q0Lk_?v) zOJmEb9F3Kurnn(NeLfzdo9S6aMUzb5pueLv^I~8~l{l#2FkuzWP|&Mbhfi)Ty|V{> znV#yV<&5gqYd#iUM6wH5){nOK31QfJDV0aim$yl_=bnEJk~ypQ-w*pKT}MTt0A>j< zI|f9Zsy};Df-Uy44$_go{xS-S#j4KS@{JCPiZyjH*J1p$y4X&7o!8XzDSn2@MrH|x z7C47qKRw>711&lArb`4j&E_I(LE`&Y6*94^*A}@6#5CJF#(v}YavrYchudANd?i1+ zNX79QUM1GIKR;elFy$ZP3O9=Ed{X@mD5=J_ENDJYOIL01W>}Nl zzHWqO7T7VJ+7H- zV$SsNvO5B6JNiuT4yk;1#wh+~hxA}-Qy0wty_V*dw#=vW*L_@u%N4ohEWjF8zTF&H zo|zU|%0VCl5l{*0!?8)3Qk}d1eERvjnF2|aqo_lUNTF4gMdeizIaAvNCF`PNrx#UY zd;!+)y8d%^czpVgU8^mNj$-xow1-9%uVPVg-h3>bIDe+7vtf_ggQZ%U2XDZ=5o_8% z9k9?cK}{#^>*fP?-zix+JvlV`iT9;B&?CkUL{zBqTMjo~F+c}fS0As))t-pi>&?<& z{{}9#VqbMEoP^GBgi~)&waki~QA94Mu&hLH8G>m&CC{by^kCrs;sj^KzIxr1 z)wIwI((LND&JG@?;YuxBw1YhRGreNbJ#vMJJBTIfa5_Ha5kTc5lfbe{yAM`@H3JWp zrx-Uw7Dp$AV%`cA2~dn49^sKnFq^{oH@;f(gw!@!*`&`~u;UVyoN;2-^c_}Us&MB{ z^nI$0FA0UUcU1N;$Hc{&Z6b{D+HU@$!D79LXoHm$E68aD&4wbXlJlZ(^$wEB{+ZhV z>@)h}nqB0lawmc3!G7EF=TuEp-7I?f-ehG5D;3>i77|3SJtbu1;^v}wL(T#=R;3$7 zN!p(fD_v}9KV=H{dOElbrExdLp9zkLr2*vpt@6G4b69NsMMYbWQ8+NJ?Y^^CqPkIO zVxR3@3oKO8uVAyv`d)0*@1p z(v7JjdCuY*JbY@uQCI^OA!aUC zq4-s*7l|4duEznlR|;`bqr2iMojWe7fa@2<*YCO0N13H8f$^|~{`H$nnb`XC3lwpt zu1)OGF2zzWSw$?*ZGrB`2Z-`rM(EIKJK7MGtswqg?=QE_cjR(E-?KjbcM!LgN@a{y zeZjo_7T86#7brNAd27$~;lm-iURJnJvr*ft95hv_RTHT1mS50pVEgn!2!E%~CbFmr z{x@uVA!S0V@TV~)h3nDu;Ud8qPPU%#BvwT@+GVMIwHGmqTL>>4UXHR4R(wi?ez_yt|W|J*2%2l?3AU(c;T=pI2hiE4;JoBz~mN3hyki^ zX9JEeDk}02Cinol%}%kPNITe<{sxT3rH>F-();zp%I2MO%AA``C38^TrBNo28`;EE z!nl>1vRk*P5)5x}YJ|o?mdPvDuI~L@wI*r>q}+^?5bvlmt!YA%5~_x4h@Xa$b`psY zp~DtrLdx#&*)`Z@N_!8ANWy|{7WTG zTPj-WTq=$kh{HVT%aOTGCun&Cbj_6oN}dj+nj9w3Q0a?<4M$#xEyfvGJD1DU*q$l* zlWwG*kB{LM4RomY$NykLGMN1j5^@|UFn8QkqJu$2g{*~~rf|z6%gxxkZJhHQ8J(HQ zCKHswVWcm&VQG8Rd*lNlAP?`#&6xR}>M`u`oMrg|)|e5YPX=p;|1IIhLb!g^cz*MI@D<{O}adCTki(F8z-Y>!OdM3xagkuoLfj>9PtG85LeFnFG1E)cFSsGCz8c zrPH&X|6WII6`_^>I5MzdO0)s84lL0tG0s+mb;i2V5#RYuqx@Lnd+t)Fmu&!`mkRBV zV4Krb&wFTl&9FW05)6;cd>Q^ThemkSsmHTtwD*dB6jmgoD1nqgN~Avg<)ck!)Jyd> zg5#`lU5A~Nmx1h=V)so%EPNq^(^ZR;zloS#CBfNQo@O+pmMMRph-SgEx#NJ*xp|jC_k@g!KXU7qX!n082uTbU%{}sYe zpPtPS>OgnuJBk|}Bt*?gzVK;HJ>BbhX;^h9{-B6O-Q>DQ%lMA~*2jMEX20bSb-PbB zB|M+yXCV_Lm*JM;ET&KFAs`!2DEmf)fTRt7uCqgV^{RaqiXh&p5y-0ro?I{QomZ0- zoF56w?bkg=QDVuj$1lNu`P<_pEwPB5TVGtBc}#;XEGw>pl39ZPnYmbaIq&IiTI=-h zK+UJo?tEf7PxjCfuhMxa6^R6+uT22=)cNFFIacam+H+C(bcs`LN3Nj0;Y{!!f5?fR z`PM*x5trK=(>eF7-?@okO{mRiDLG!mCy@k6y92LkmMWD#6tUS~++DmLFemaHW;`iNx0*fFMkUxiQI4dJc()y|KziX+yGf>o zQhe=$wnZbP&$e(38MeR8DebfopLSE&h|pzAYvt@5lw=}q^Nmig+{j6{zEv( zctro@Mw$y_>)$UgWtU5@b-+1IAEra;R(qo9EVWBY{O(V1qaAGaoyftEA9SruNz{^d zPG{8L89`uM{L4D4HYaX7V_G@S)x}KBWA~Z;0q7c97NlQ1Z7G7@d)LdJAKkK7ax>2n zMBXnB%lPk@I@f=f%iw5AoyS9vV#FhtPJH2Vwt`~~8E-+E-qObo%RY1zPkzIg$%_ql zsG2kWfj2ABD9g;c|L^rHmqZ7CKzY}Vh=4X=z*CR`YO{V+dxuu)D*^@y`1c`3&jD-2 zX|+)ApK?3VR+k1)e5789=5}u<5&tEZ?-hFHW5b8vp=WcoNzx}Tc`x?sBQGC|49qt_*#zRkoy*CxeWQy{5(Qt{jPi4jQ zK*5G9;qkMRq%i#ymy|#f6CBuV)@H^>%kNJ_*G-8Y^(weEY)0!n7+MTRaM!VsqRBb` zvjislFOaA~W2I9V?qOFyErY2)b7H1lSjL`H5N;_^qA!>|E;)XDFfYAY+XNBOKF!QS z?T#=XDq-tcASoV8z#+UuXAG-r@bdOb21++}#G5~yOMHnf5Pe7N`~2c&L_(L)+GdvL zKm>tt%#oEsPF$Q1Os`&xUFdu2&vC7S(5$&(tmoZowlU*rhb z@8vF8I1~wDA!AjZJ7~AZ&c<<=iugT6%muIisH}@*+yl@q_LwBe3nO={Vf-hshHH`R z;3A>JnfCXW{l0)$)#mOB=>1|Ew!~WxwGR4BQ(BL%1YSN#8gq(#vD9!L#XkoMc3CmU z$A8+HIy+iFUc812`w*f;X=5OmdJN%hV9d<3f8$~6k+~NIKRz|4bBgcv1p4UP!F%dO zBULU5jxS`H58nGhhvpxz>p)Yd#lUkp>VQ{n3zFwwS`U{0<`{G^IY&l7*UXYev8Xu3 zZRqw0#?rA=Unf2`dz)f$2^Xw>((1XH;E)t(Fj^k-BgHi(eM0ZcEm`_B`4H%_irgE9 zGCk&u{CGA{ssyM|+?*BXt!>ybyxw`t`?p6Y!=%v4eWv2T|WHI%)5DhjAj zj`%n(MI%izgLaDC$lK3WlBA@QqpWcH)&dM@9t_#a%QT=2E*H$O>Lt+9d8JSeMj){- z*>p5X-@>zBw8l#9A=~bdx~3(!9Div~NqJnuQkUap^O^_+`Y4+%NW0AOKcnHjZIHq^ z1NxdjU+}$_({L?Es-{v;i)vJZeMg+yn#-gea%S3^)(M>H{-;S5@tjE2Yb_if z{rF)#Qa)`vYfbf(48~e!Mw`3V@U!jkq+Pj+^|{FTX<`_f;Th zCu%iRLtl%y#&^$QByInVBf+Q8uiF5qA;lH-;V2M_uZ|mF^y;XOLo{WKAeXm!5WdLQ zI2SzZo_0`}`M~uZ*UG^nt!ojyN_UZ*ScDO3Ig0kL-53d-NFt-RIbf!1^~_E{)g<}t z(G@Q_toHQh@^fWHN@6`{SgQnggicbN@~wxek}u^x(T{}0P)6DGj(YP`aetQ$ID!lG zo;qDPhkvtRxbTf)Gpki8iLAJNC_&=7y3%)#_&)vH3dA28d$nj;iu61Mt%;v~hpA?7 zEgx>PIZ;ro>RC8H^g<;l-^lU@jzYm(Npsq=ipr9q0!pIG(dDJqi33u+pDIxD*jAkX z#b#`47(g!1)d)@RH>3pD0-i7yiW>l3tleWk#OlCkxI{yuBJK0p-~~gD0l^F&ZyGR( zqTkKO>U3DYEq-<{6CYG9zn;=$d0yT|zXlcy|b zt*4cr>9fSLo>Qu&mCMue`-w>rFckdpD?D(7bRJ)eQtL_ecPLw2qa(DXZWiw292h$c zw3|0jtr4Xu86lE!Ae?_=n}AOi8NWv5Qb><-WXJJl44DD?KOFz;Uyaj6KJ}Z7>RIhJ z;0H*jxOd~%S0|{=eiujbN-)YY?RQL5B>9EG2k?93G5AcdT%Ql6C@_5}DR{|-IY-jd z&NnjhJJZIB<#F+$LAhD9H)p``ml3Qt+luzPDHvvG^SF#=$$iNb(Bh~^%xgiiKkf{* zST1Yvy}NqfoP=h->@~fJ!>!ja;xN3N{V{6q&thD-6Mu*p78+9Ejb9v>64%!}hGxqrz@K}{q%t5Hd)3d$=uvN5 zulNAsqnLRjU$~|A*VrsxBK}EHwF?@}r>1>MH;8=>y-5;0Iyxt!i`Xk)eAEXAlO)8x zq*L0~{F(a`eaC7?%zhxN`amF61$B)Ha&{x!2Dr!PcAJiLzeTc3mSpleh7j0^XstR+ z-YX@R)@y(e)38dR?mKP1%iJ~TyrrA3Evrt?o|9O5`EEoQPR`?#yfsFyuzR5-)AduV z?BCVuSf?g(o2w1KR}16pk~p+HL~4=4K!i=su1GrO&cp}X`>i124;#CsV=rtGXLzF> zUrM$GIQ+xKiCqxYsr(5f1^pHBk7@YMnFkk0(w0)vddHP$oWv+cpCyZtha%jP(ne|+ zIK|XI_W#RqOC#KWZ$;#r-eT5_wqqi$dg}XW+i$RFmTIa?-TW+ftr5@<3nwI6QKUOT zkh%5$-QUb4p_dRGXh7N7p%P4(RoF;j`#l=Q(8Qh9y5IeKeUf6)n?7V3Wu8tTP0vJ< zcA6ekW>DFLuUU zVmRZ8_xDV*b4H1wMo?C>bi(78fCc9_Sd->Rc{{DXS#4Y;>*#A+?UD|!zU37v-`Xul z_6;wD{!pybE-GHBD22SsHxKWXz%%|2e{&Xy#`aQte)kh*2NQPXO`Ky$Es=!(e3*Sr zKcNGBqsdvDCACld@*Ho`$cHfPR|060V3@d%165$Qx&K=6m{(8x$ zZFMcao0tN87EZ zoKz%g8zt-UHbYJF@I*~=%1(0gdP75z90*vzis=)pA98puIHOYaIcE)A5*~m}@W|vs z7_|iv%lX9TF%#<1#V^>+11?_pM8r$I=0nXQ=aBaWCOREmFp0F!dg_aHrDIQf#6zfA^x zQiOx56$%0xo}*55!K_G6s_!R8x0ZeeU%ohaq3b!76WpsM(`g0=R~yv`-^(j?zk*6C z)GlHFGxR6aMr3=gPLA6zNR-^yx?Th|b?XOEnA>6`7wFFv!(c!Q+!!^GFxn&X|re_LPI`DOR0 zx9~ESWXTe0i9GO_b=TRswkk{G8985+GZ)Vc#zzN{E@YnN#^(RV*)chltybl*7GCF|DFV4UJ{sr4{6) zrbtGqY&dbFHx+qqF_#SAUjSNUEdPcVu`+qvlT=)^$%5m1$#i_0Wy^}1B=SUOQjU_# z0X^DEY`8OAQ_sQnEN2;v2 z{0FCB(T)txV}NQ|3I98w3;fM=I+7kmXQwdJ1;Fwk1_QwM27B33Ut|CVi8jni0a#2p4Pr zGgO=kD|);@xKINru8$uv)_%1b_sZk|=h5HhONAZZh{8M#o)&5K6qH;8ax=|Na!cK@ z3}!*<;0#9#Dyir0*Drlsde_XDHuSfFKJU#C?i_m@;>YGeu_v+e|m^M zr;mr!!uNOz3hdF0NGTlt5>b`SK zsq}7t+tTx;!g61d5P@fw3x`l4)9H)u;@`6t`eA%gkiT*6T+|ryoQvj*B4{0om&z;u zIXcLvS@gZ39A?)lNIj;Bd>*ohX*J8ep@Lpm_fz{_5)Hu@ry%B*r<(gyGrl6jr=oKX_T+_34|9F8jdBaIf zUqIw8a`tDdVj`9=f`3ljBg}rGp0v4j1bef!X%($2Y;~JO;XF^l_vdJ@=h4{&+uq%R zc46$+wn$31IBU|%ZXHsQ?Hc`48(9x58V+g&%Nsk$lx|Y8eZli1?>rHiXFf2oB0C>9 z0H!+(exE^}M(?N1N%b_bGy%`!WMfw2ppd0e)y)$15zX>gxR&0ahjJ=wi2(a51XJ{_ zzZWJ{3Ya+6C1jA%RUx7HJdxOKN8y2hl1LVQF=}Bn-^IpL@oMZJc~N4tK#wp3e-nRG zP}6hHvu{w#bD)=1-qmtH(<3EM^sy5u5uPx&qBF+#PP`h}dN`lI3D!}Zim|n2wX3nr zfG|n)jyIY->#CB9bd`Nw^#l=nvjK;vU=vlL5*eULz@Df{U)S#Q@p_=M!ipZ@FN$^e z@HbI6W2p>!iMBYsUH!3KoIs9gubp|dB#lw;h+42bX&))hZLA7r2FL#9!LJW%&KrJu zE_Ezgo^^Zr?TIm7|2&NG8HGgT{#BhMD)o1THiG_mJ8|fxqWQ}@lj1MB`cn6sd|Tz0 zEf*xT+=f^H-jrbSiUSgp9nduLmAcU*scdmKd0tzRF==^2y%XE26JXtQW>VEtP>F(u zC8_I79R#5*)1NdB&0gl5%fgs}0#V1?q#sWgVZ=j+%zN6OzpnJ+2`<-dtplPxv(gCp zXqRZ#10uWaC75p8597Nk(k+Ams&^?)FWn#3UBhr#Cjl=sY}fUFz1Q!m@|v9DqgP?XbR3z zuwGKW5z~gQ(vTndtzyRftq7xH_LYr;ZEO0tNs`08X$qs>Bu~_pXA;5o^as2>tKDMj1PD+Vb>vkt(Tl`SUpxu_+VsSv3xTS^(1V7$Vt*|$S# zND6nM*Sa8=j;;SECE>ls!o()`=xFl|E0b1dc)f8N&dmBj!-9t^r%$t*C22{3UB^v?3<6+W3K$8YGLuV*NJ(R_?%DFqRdApdZqM{Kx~L)x|*uy zC|eNqSBkV=A?!j*0{-EzS$ma5buJM_6={opzWWQ%ZtzG zYfG}z5-%LITgv(VoZA>PeL&Jmrj&1Hv*}V>>@W{4>_HgFM;&G|LMw8f`U%|?>N!vl zF>=H@(TKSiMKf~OPt5C9oh_aqNmr>b5FluK(sh=QK3?W%qfYm%B#^-o5hH2BD%5{u zd#|wdVi4U~E|5(hqc~N?^cG?dgBulzS(LxnkaFk(41Z6(lQlNZJw5mw?ka^3YAw_^ z!Er8(>$y9u;m4Ll zH+A1XULfgh>d3{&giI#R%>tvKzCEs2Ho|{E(KbZRi)NtE_dLK&khKq`&d#xPd@hyF0Sn6w%LSeBor~8h z$qBF~Ve#rYg8>52V;`>B1Q&sEY#pgR~wL#yk1icP(SjJzs0 zi)4qH$Bj-l%>HPNCwio3Cz(cBy5m^T8iytSyqXsxs8qu}_@ay6OfB{kj!yYe$rhCC za8-SZdrD%m`x!}%o9i$GDg3HlJ{50#tLf8c(qG?PDMLtTs z9z9>59hq(cn8gNycy3TLfR&tov`O?^`+HPGU-Vp1AC*)C0$rPVdDc;O4(&P*IVv7h zgfV*qud*v9-L(@CsQ|NR^vINvKG#+4+mDX_0)E=Dh*XM2*%%d?snuT^m*X!VC>yYS zzsMY&-R|}@y=q=wQA#GK_UDHi&&sZYf}p#Ip@_nIc}Q!`OstI^g2GDzz65MYCjFw-%<5)_1|Yfy|0hYS(9!A-; z+Vr9x7^s+zH;G*C`_Ovw`(@p2Lu2;qfGlp3ej{iM7>jZ9>@eyId$Lw!SQc57$v_9} z8>IpBhcj1J3RsMayd$LC>R?2;b{cb*=7%P?cE!LsQD}O(MC0Ee$~P7Ka)WJ)=Ii=Z zwKTFW=5kBkx@VVAjt9P<@Dym3^j$uFr=9e$;i(bn(5JdDY}ZHRDQG^tHkTw-GcKH1 zJ#>irI+>R#?N@tc7(KF&Nug1lE}(N0`nBApAEdpd7Te91g|X>W?7Fb4GL{Ztco0@j z^R?OxE3Fl&@~v+Om&3=j{=egACV@%#DqfO+l6(0zll)@|@B9MK&mf4lS*Ppp*!W)5 zL%Y-^{YH1$QiS|sc25gt$gvT!!mxCeX!QW=@*b@q>F)=S3+np~08Qjd2UYaXk|=?G z4?!Y<8r)^~cgmX)?)1bSU~C~7XAb0)>_zTo-T-SaS}le#eb(^l^hlpio`=^pv7pzr z)*LB1WUiqkp)Pcj8I8D<&D3S1gXny>B6`fuj$i0kcSx)Sq~miSeYJ7)U#L<|`)3~Q z99a={ZjX~bv+Vz{8FAfyrW=$P=|FY4UE^;2f+amuQvPxj1C>-muy2a8ZItQ{=0hdR zJdN|uqJT`p9Qr-Eapi8Iv|L2;)DtvSo?#iF3N4F|Gb#;QWI?TH?}-)h$j&WXD+vW=VNVwu@CJG!sPgN#M0CnQrY zSm>kB41a`ZgQ419U_}E4A4uT+wET)iRaWx~E^MT`%qJ+Oh*I3`-CXM(kvmATRlQPH@h%pbwbXjd6VBRL9j&T>ofiL- z*&%4S!t$@x`>MMr+JC?4Nw8p3{mhh$V0kM76*B5&EmgCk%~?X{R5cnA(JJyaU*jjR zA!aC&nu3AXAxWz-18w$6X!YIs{R6W_#waZzMeJGi<>IJE6Tj)^L1F$JbWh5!Hfp(# zS7uJa%hSH|7=YJ%K$!5qoe~yZkFZb*|A}+7%8P$dH;wcp<&8Hqab!Q$Rh;5-)KU8b zlMtB5Tr+?p9rfd_!Mkp1`NmGLYZz8NX*!!!Uy4%ya*;*)VO_siuW~H?)t)fqINDmG z?M-}6k{}sDQXCp?#uB>tDnVJ?2TBetoc=|A1XtqsbfbN6M@_oe63@yXctbuHs1jm7 z=|)7+Fs(L+q-$zkV_#T)Xq^j$L&`TopCV~KWu&I7mx@M8RIJhWd17w8y7>^YWoMum zLRa2(Fs|pO<5qn1yQQY@Tw=y}dnTXaFuzOIVpEp8=*eQN0r@25Gj0#Vdg)2udwfmQ z_eOy4LImfh&iAlSq91r!+4IotP;6ciHU)1i`L4O9FB=g%^chRiKm*umHtF(%pz=@} zf4{x^QqS!jVuOPD66;!9(q#-xR35rNN!D)jB**kVo>1f%whPjeK;LYUOX{U{fC$+V zGWuHn*>4o{HE0Kh0i)f&e+RRt6mpBr6`#Wu2HI&8onPFxBq$LU3^T}h`jz5gfAiG} zOKqvOqDH|1nS<#K5PtZipL}|X^&B&{@90OwfP82zN`w>a zF>I27D#$TLC&2`y@GZOFq|}c}iYw_UL@e|n^f8zNdKJYYibd|41QpRk1e>aEgPlw^ zs<@8RhNHhS?iz-0&y!~jqkGHvK`hVzold>zSK8xreg{;GNnoSV&V;Pto?+6D)2nq; zw8vDRT@ovw#Pd)vRFvY&8B^cNq51m(LQXq#^wo6e8uGN(a;E+Y)ytlUC>43eOlotH zzhQ+RJu-l4pdxuQ{i zl$PzkUhZz595QVK$yUOhETBSKy&ANKkh1d*u-Vs34Ih(Dr6`%8QNWi;9CG!~o7R8# zmo(C!^5r<`h@5|OhM3Jh%UyXtt@W7wXeaJ*)+Rzt7t~DxP?3y=<9C#!~>KbGp_y?V(t&3Ua+`P;0uAD{LcyHHu%Vrj*A~Y)k!l z$YDT^#upAd34C(Y^YLzR!dA$pCqbajR44&7z{zf{NiSAyy$=~;>%0AA$1)$U2Gjkx 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%0Aks%mL%5lo&VMU4HIM5k=tP#R-ZrR)@!_ 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�u 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 literal 0 HcmV?d00001 From fc774957fe88e7007c289b1203094284af502455 Mon Sep 17 00:00:00 2001 From: Krishna Srinivas Date: Tue, 9 May 2017 07:53:31 -0700 Subject: [PATCH 12/80] gateway: reject requests with unknown authorization (#4297) --- cmd/gateway-handlers.go | 38 ++++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/cmd/gateway-handlers.go b/cmd/gateway-handlers.go index 269a3e31c..bce48a8dd 100644 --- a/cmd/gateway-handlers.go +++ b/cmd/gateway-handlers.go @@ -65,6 +65,12 @@ func (api gatewayAPIHandlers) GetObjectHandler(w http.ResponseWriter, r *http.Re writeErrorResponse(w, s3Error, r.URL) return } + case authTypeAnonymous: + // No verification needed for anonymous requests. + default: + // For all unknown auth types return error. + writeErrorResponse(w, ErrAccessDenied, r.URL) + return } getObjectInfo := objectAPI.GetObjectInfo @@ -239,10 +245,6 @@ func (api gatewayAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Re var objInfo ObjectInfo switch reqAuthType { - default: - // For all unknown auth types return error. - writeErrorResponse(w, ErrAccessDenied, r.URL) - return case authTypeAnonymous: // Create anonymous object. objInfo, err = objectAPI.AnonPutObject(bucket, object, size, r.Body, metadata, sha256sum) @@ -274,6 +276,10 @@ func (api gatewayAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Re } // Create object. objInfo, err = objectAPI.PutObject(bucket, object, size, r.Body, metadata, sha256sum) + default: + // For all unknown auth types return error. + writeErrorResponse(w, ErrAccessDenied, r.URL) + return } w.Header().Set("ETag", "\""+objInfo.MD5Sum+"\"") @@ -313,6 +319,12 @@ func (api gatewayAPIHandlers) HeadObjectHandler(w http.ResponseWriter, r *http.R writeErrorResponse(w, s3Error, r.URL) return } + case authTypeAnonymous: + // No verification needed for anonymous requests. + default: + // For all unknown auth types return error. + writeErrorResponse(w, ErrAccessDenied, r.URL) + return } getObjectInfo := objectAPI.GetObjectInfo @@ -721,6 +733,12 @@ func (api gatewayAPIHandlers) ListObjectsV1Handler(w http.ResponseWriter, r *htt writeErrorResponse(w, s3Error, r.URL) return } + case authTypeAnonymous: + // No verification needed for anonymous requests. + default: + // For all unknown auth types return error. + writeErrorResponse(w, ErrAccessDenied, r.URL) + return } // Extract all the litsObjectsV1 query params to their native values. @@ -785,6 +803,12 @@ func (api gatewayAPIHandlers) HeadBucketHandler(w http.ResponseWriter, r *http.R writeErrorResponse(w, s3Error, r.URL) return } + case authTypeAnonymous: + // No verification needed for anonymous requests. + default: + // For all unknown auth types return error. + writeErrorResponse(w, ErrAccessDenied, r.URL) + return } getBucketInfo := objectAPI.GetBucketInfo @@ -835,6 +859,12 @@ func (api gatewayAPIHandlers) GetBucketLocationHandler(w http.ResponseWriter, r writeErrorResponse(w, s3Error, r.URL) return } + case authTypeAnonymous: + // No verification needed for anonymous requests. + default: + // For all unknown auth types return error. + writeErrorResponse(w, ErrAccessDenied, r.URL) + return } getBucketInfo := objectAPI.GetBucketInfo From 298b470f698686e7e79e2fa8c2a5865e34ce61bd Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Tue, 9 May 2017 14:32:24 -0700 Subject: [PATCH 13/80] fs/erasure: Ignore objects with / even for DeleteObject() (#4303) Additionally GetObject() also returns errFileNotFound similar to HeadObject(). Fixes #4302 --- cmd/fs-v1.go | 9 ++------- cmd/object-api-input-checks.go | 4 ++++ cmd/object-api-utils.go | 5 +---- cmd/server_test.go | 33 +++++++++++++++++++++++++++++++++ cmd/xl-v1-object.go | 8 ++------ cmd/xl-v1-object_test.go | 4 ++-- 6 files changed, 44 insertions(+), 19 deletions(-) diff --git a/cmd/fs-v1.go b/cmd/fs-v1.go index da49092ca..0fc98a03f 100644 --- a/cmd/fs-v1.go +++ b/cmd/fs-v1.go @@ -473,6 +473,7 @@ func (fs fsObjects) CopyObject(srcBucket, srcObject, dstBucket, dstObject string // startOffset indicates the starting read location of the object. // length indicates the total length of the object. func (fs fsObjects) GetObject(bucket, object string, offset int64, length int64, writer io.Writer) (err error) { + // This is a special case with object whose name ends with if err = checkGetObjArgs(bucket, object); err != nil { return err } @@ -568,12 +569,6 @@ 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 } @@ -591,13 +586,13 @@ func (fs fsObjects) GetObjectInfo(bucket, object string) (ObjectInfo, error) { // for future object operations. func (fs fsObjects) PutObject(bucket string, object string, size int64, data io.Reader, metadata map[string]string, sha256sum string) (objInfo ObjectInfo, retErr error) { var err error - // This is a special case with size as '0' and object ends with // a slash separator, we treat it like a valid operation and // return success. if isObjectDir(object, size) { return dirObjectInfo(bucket, object, size, metadata), nil } + if err = checkPutObjectArgs(bucket, object, fs); err != nil { return ObjectInfo{}, err } diff --git a/cmd/object-api-input-checks.go b/cmd/object-api-input-checks.go index 2975f117b..c7d47feeb 100644 --- a/cmd/object-api-input-checks.go +++ b/cmd/object-api-input-checks.go @@ -36,6 +36,10 @@ func checkBucketAndObjectNames(bucket, object string) error { } // Verify if object is valid. if !IsValidObjectName(object) { + // Objects with "/" are invalid, verify to return a different error. + if hasSuffix(object, slashSeparator) || hasPrefix(object, slashSeparator) { + return traceError(ObjectNotFound{Bucket: bucket, Object: object}) + } return traceError(ObjectNameInvalid{Bucket: bucket, Object: object}) } return nil diff --git a/cmd/object-api-utils.go b/cmd/object-api-utils.go index b313b5e6c..acb14f508 100644 --- a/cmd/object-api-utils.go +++ b/cmd/object-api-utils.go @@ -119,10 +119,7 @@ func IsValidObjectName(object string) bool { if len(object) == 0 { return false } - if hasSuffix(object, slashSeparator) { - return false - } - if hasPrefix(object, slashSeparator) { + if hasSuffix(object, slashSeparator) || hasPrefix(object, slashSeparator) { return false } return IsValidObjectPrefix(object) diff --git a/cmd/server_test.go b/cmd/server_test.go index 2a6dfc120..a3222d499 100644 --- a/cmd/server_test.go +++ b/cmd/server_test.go @@ -171,6 +171,39 @@ func (s *TestSuiteCommon) TestObjectDir(c *C) { c.Assert(err, IsNil) verifyError(c, response, "XMinioInvalidObjectName", "Object name contains unsupported characters.", http.StatusBadRequest) + + request, err = newTestSignedRequest("HEAD", getHeadObjectURL(s.endPoint, bucketName, "my-object-directory/"), + 0, nil, s.accessKey, s.secretKey, s.signer) + c.Assert(err, IsNil) + + client = http.Client{Transport: s.transport} + // execute the HTTP request. + response, err = client.Do(request) + + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusNotFound) + + request, err = newTestSignedRequest("GET", getGetObjectURL(s.endPoint, bucketName, "my-object-directory/"), + 0, nil, s.accessKey, s.secretKey, s.signer) + c.Assert(err, IsNil) + + client = http.Client{Transport: s.transport} + // execute the HTTP request. + response, err = client.Do(request) + + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusNotFound) + + request, err = newTestSignedRequest("DELETE", getDeleteObjectURL(s.endPoint, bucketName, "my-object-directory/"), + 0, nil, s.accessKey, s.secretKey, s.signer) + c.Assert(err, IsNil) + + client = http.Client{Transport: s.transport} + // execute the HTTP request. + response, err = client.Do(request) + + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusNoContent) } func (s *TestSuiteCommon) TestBucketSQSNotificationAMQP(c *C) { diff --git a/cmd/xl-v1-object.go b/cmd/xl-v1-object.go index ce9d8ba29..d7b041387 100644 --- a/cmd/xl-v1-object.go +++ b/cmd/xl-v1-object.go @@ -320,12 +320,6 @@ 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 } @@ -467,6 +461,7 @@ func (xl xlObjects) PutObject(bucket string, object string, size int64, data io. if isObjectDir(object, size) { // Check if an object is present as one of the parent dir. // -- FIXME. (needs a new kind of lock). + // -- FIXME (this also causes performance issue when disks are down). if xl.parentDirIsObject(bucket, path.Dir(object)) { return ObjectInfo{}, toObjectErr(traceError(errFileAccessDenied), bucket, object) } @@ -480,6 +475,7 @@ 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). + // -- FIXME (this also causes performance issue when disks are down). if xl.parentDirIsObject(bucket, path.Dir(object)) { return ObjectInfo{}, toObjectErr(traceError(errFileAccessDenied), bucket, object) } diff --git a/cmd/xl-v1-object_test.go b/cmd/xl-v1-object_test.go index 40b162a5e..86ffd8648 100644 --- a/cmd/xl-v1-object_test.go +++ b/cmd/xl-v1-object_test.go @@ -73,9 +73,9 @@ func TestXLDeleteObjectBasic(t *testing.T) { {".test", "obj", BucketNameInvalid{Bucket: ".test"}}, {"----", "obj", BucketNameInvalid{Bucket: "----"}}, {"bucket", "", ObjectNameInvalid{Bucket: "bucket", Object: ""}}, - {"bucket", "obj/", ObjectNameInvalid{Bucket: "bucket", Object: "obj/"}}, - {"bucket", "/obj", ObjectNameInvalid{Bucket: "bucket", Object: "/obj"}}, {"bucket", "doesnotexist", ObjectNotFound{Bucket: "bucket", Object: "doesnotexist"}}, + {"bucket", "obj/", ObjectNotFound{Bucket: "bucket", Object: "obj/"}}, + {"bucket", "/obj", ObjectNotFound{Bucket: "bucket", Object: "/obj"}}, {"bucket", "obj", nil}, } From fa3f6d75b672059d52c4e676ec305c8d64c5f7ac Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Tue, 9 May 2017 17:46:46 -0700 Subject: [PATCH 14/80] fs: Verify if parent is an object before i/o. (#4304) PutObject() needs to verify and fail. Fixes #4301 --- cmd/fs-v1-helpers.go | 2 +- cmd/fs-v1-helpers_test.go | 5 +-- cmd/fs-v1-multipart.go | 5 +++ cmd/fs-v1.go | 30 ++++++++++++++++- cmd/fs-v1_test.go | 48 ++++++++++++++++++++++++++++ cmd/object-api-getobjectinfo_test.go | 4 +-- cmd/object_api_suite_test.go | 37 +++++++++++---------- 7 files changed, 108 insertions(+), 23 deletions(-) diff --git a/cmd/fs-v1-helpers.go b/cmd/fs-v1-helpers.go index 13dc04616..47dbb160f 100644 --- a/cmd/fs-v1-helpers.go +++ b/cmd/fs-v1-helpers.go @@ -174,7 +174,7 @@ func fsStatFile(statFile string) (os.FileInfo, error) { return nil, traceError(err) } if fi.IsDir() { - return nil, traceError(errFileNotFound) + return nil, traceError(errFileAccessDenied) } return fi, nil } diff --git a/cmd/fs-v1-helpers_test.go b/cmd/fs-v1-helpers_test.go index 49003f686..2937a64b8 100644 --- a/cmd/fs-v1-helpers_test.go +++ b/cmd/fs-v1-helpers_test.go @@ -134,7 +134,7 @@ func TestFSStats(t *testing.T) { srcFSPath: path, srcVol: "success-vol", srcPath: "path", - expectedErr: errFileNotFound, + expectedErr: errFileAccessDenied, }, // Test case - 6. // Test case with src path segment > 255. @@ -167,7 +167,8 @@ func TestFSStats(t *testing.T) { for i, testCase := range testCases { if testCase.srcPath != "" { - if _, err := fsStatFile(pathJoin(testCase.srcFSPath, testCase.srcVol, testCase.srcPath)); errorCause(err) != testCase.expectedErr { + if _, err := fsStatFile(pathJoin(testCase.srcFSPath, testCase.srcVol, + testCase.srcPath)); errorCause(err) != testCase.expectedErr { t.Fatalf("TestPosix case %d: Expected: \"%s\", got: \"%s\"", i+1, testCase.expectedErr, err) } } else { diff --git a/cmd/fs-v1-multipart.go b/cmd/fs-v1-multipart.go index 040a7a090..14c3c1c12 100644 --- a/cmd/fs-v1-multipart.go +++ b/cmd/fs-v1-multipart.go @@ -706,6 +706,11 @@ func (fs fsObjects) CompleteMultipartUpload(bucket string, object string, upload return ObjectInfo{}, err } + // Check if an object is present as one of the parent dir. + if fs.parentDirIsObject(bucket, pathutil.Dir(object)) { + return ObjectInfo{}, toObjectErr(traceError(errFileAccessDenied), bucket, object) + } + if _, err := fs.statBucketDir(bucket); err != nil { return ObjectInfo{}, toObjectErr(err, bucket) } diff --git a/cmd/fs-v1.go b/cmd/fs-v1.go index 0fc98a03f..5d6c64bde 100644 --- a/cmd/fs-v1.go +++ b/cmd/fs-v1.go @@ -25,6 +25,7 @@ import ( "io/ioutil" "os" "os/signal" + "path" "path/filepath" "sort" "syscall" @@ -473,7 +474,6 @@ func (fs fsObjects) CopyObject(srcBucket, srcObject, dstBucket, dstObject string // startOffset indicates the starting read location of the object. // length indicates the total length of the object. func (fs fsObjects) GetObject(bucket, object string, offset int64, length int64, writer io.Writer) (err error) { - // This is a special case with object whose name ends with if err = checkGetObjArgs(bucket, object); err != nil { return err } @@ -580,6 +580,25 @@ func (fs fsObjects) GetObjectInfo(bucket, object string) (ObjectInfo, error) { return fs.getObjectInfo(bucket, object) } +// This function does the following check, suppose +// object is "a/b/c/d", stat makes sure that objects ""a/b/c"" +// "a/b" and "a" do not exist. +func (fs fsObjects) parentDirIsObject(bucket, parent string) bool { + var isParentDirObject func(string) bool + isParentDirObject = func(p string) bool { + if p == "." { + return false + } + if _, err := fsStatFile(pathJoin(fs.fsPath, bucket, p)); err == nil { + // If there is already a file at prefix "p" return error. + return true + } + // Check if there is a file as one of the parent paths. + return isParentDirObject(path.Dir(p)) + } + return isParentDirObject(parent) +} + // PutObject - creates an object upon reading from the input stream // until EOF, writes data directly to configured filesystem path. // Additionally writes `fs.json` which carries the necessary metadata @@ -590,6 +609,10 @@ func (fs fsObjects) 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. + if fs.parentDirIsObject(bucket, path.Dir(object)) { + return ObjectInfo{}, toObjectErr(traceError(errFileAccessDenied), bucket, object) + } return dirObjectInfo(bucket, object, size, metadata), nil } @@ -597,6 +620,11 @@ func (fs fsObjects) PutObject(bucket string, object string, size int64, data io. return ObjectInfo{}, err } + // Check if an object is present as one of the parent dir. + if fs.parentDirIsObject(bucket, path.Dir(object)) { + return ObjectInfo{}, toObjectErr(traceError(errFileAccessDenied), bucket, object) + } + if _, err = fs.statBucketDir(bucket); err != nil { return ObjectInfo{}, toObjectErr(err, bucket) } diff --git a/cmd/fs-v1_test.go b/cmd/fs-v1_test.go index f0b68b111..366ec010f 100644 --- a/cmd/fs-v1_test.go +++ b/cmd/fs-v1_test.go @@ -523,6 +523,54 @@ func TestFSGetBucketInfo(t *testing.T) { } } +func TestFSPutObject(t *testing.T) { + // Prepare for tests + disk := filepath.Join(globalTestTmpDir, "minio-"+nextSuffix()) + defer removeAll(disk) + + obj := initFSObjects(disk, t) + bucketName := "bucket" + objectName := "1/2/3/4/object" + + if err := obj.MakeBucket(bucketName); err != nil { + t.Fatal(err) + } + sha256sum := "" + _, err := obj.PutObject(bucketName, objectName, int64(len("abcd")), bytes.NewReader([]byte("abcd")), nil, sha256sum) + if err != nil { + t.Fatal(err) + } + _, err = obj.PutObject(bucketName, objectName+"/1", int64(len("abcd")), bytes.NewReader([]byte("abcd")), nil, sha256sum) + if err == nil { + t.Fatal("Unexpected should fail here, backned corruption occurred") + } + if nerr, ok := errorCause(err).(PrefixAccessDenied); !ok { + t.Fatalf("Expected PrefixAccessDenied, got %#v", err) + } else { + if nerr.Bucket != "bucket" { + t.Fatalf("Expected 'bucket', got %s", nerr.Bucket) + } + if nerr.Object != "1/2/3/4/object/1" { + t.Fatalf("Expected '1/2/3/4/object/1', got %s", nerr.Object) + } + } + + _, err = obj.PutObject(bucketName, objectName+"/1/", 0, bytes.NewReader([]byte("")), nil, sha256sum) + if err == nil { + t.Fatal("Unexpected should fail here, backned corruption occurred") + } + if nerr, ok := errorCause(err).(PrefixAccessDenied); !ok { + t.Fatalf("Expected PrefixAccessDenied, got %#v", err) + } else { + if nerr.Bucket != "bucket" { + t.Fatalf("Expected 'bucket', got %s", nerr.Bucket) + } + if nerr.Object != "1/2/3/4/object/1/" { + t.Fatalf("Expected '1/2/3/4/object/1/', got %s", nerr.Object) + } + } +} + // TestFSDeleteObject - test fs.DeleteObject() with healthy and corrupted disks func TestFSDeleteObject(t *testing.T) { // Prepare for tests diff --git a/cmd/object-api-getobjectinfo_test.go b/cmd/object-api-getobjectinfo_test.go index 14730eede..bdba72689 100644 --- a/cmd/object-api-getobjectinfo_test.go +++ b/cmd/object-api-getobjectinfo_test.go @@ -69,8 +69,8 @@ func testGetObjectInfo(obj ObjectLayer, instanceType string, t TestErrHandler) { {"test-getobjectinfo", "Antartica", ObjectInfo{}, ObjectNotFound{Bucket: "test-getobjectinfo", Object: "Antartica"}, false}, {"test-getobjectinfo", "Asia/myfile", ObjectInfo{}, ObjectNotFound{Bucket: "test-getobjectinfo", Object: "Asia/myfile"}, false}, // Test case with existing bucket but object name set to a directory (Test number 12). - {"test-getobjectinfo", "Asia", ObjectInfo{}, ObjectNotFound{Bucket: "test-getobjectinfo", Object: "Asia"}, false}, - // Valid case with existing object (Test number 13). + {"test-getobjectinfo", "Asia/", ObjectInfo{}, ObjectNotFound{Bucket: "test-getobjectinfo", Object: "Asia/"}, false}, + // Valid case with existing object (Test number 14). {"test-getobjectinfo", "Asia/asiapics.jpg", resultCases[0], nil, true}, } for i, testCase := range testCases { diff --git a/cmd/object_api_suite_test.go b/cmd/object_api_suite_test.go index 73af5b23d..3f0af2d25 100644 --- a/cmd/object_api_suite_test.go +++ b/cmd/object_api_suite_test.go @@ -749,23 +749,26 @@ func testGetDirectoryReturnsObjectNotFound(obj ObjectLayer, instanceType string, c.Fatalf("%s: %s", instanceType, err) } - 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()) + testCases := []struct { + dir string + err error + }{ + { + dir: "dir1/", + err: ObjectNotFound{Bucket: bucketName, Object: "dir1/"}, + }, + { + dir: "dir1/dir3/", + err: ObjectNotFound{Bucket: bucketName, Object: "dir1/dir3/"}, + }, + } + + for i, testCase := range testCases { + _, expectedErr := obj.GetObjectInfo(bucketName, testCase.dir) + if expectedErr != nil { + expectedErr = errorCause(expectedErr) + if expectedErr.Error() != testCase.err.Error() { + c.Errorf("Test %d, %s: Expected error %s, got %s", i+1, instanceType, testCase.err, expectedErr) } } } From d1971b9a4d43eb51c605bf1c6769b748f32136bd Mon Sep 17 00:00:00 2001 From: poornas Date: Wed, 10 May 2017 09:52:31 -0700 Subject: [PATCH 15/80] Prevent duplicate policy rows from being created (#4276) --- browser/README.md | 2 +- browser/app/js/components/PolicyInput.js | 21 ++++++++++++++++++--- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/browser/README.md b/browser/README.md index 647147fdf..9a65c4eb2 100644 --- a/browser/README.md +++ b/browser/README.md @@ -27,7 +27,7 @@ go get github.com/elazarl/go-bindata-assetfs/... yarn release ``` -This generates ui-assets.go in the current direcotry. Now do `make` in the parent directory to build the minio binary with the newly generated ``ui-assets.go`` +This generates ui-assets.go in the current directory. Now do `make` in the parent directory to build the minio binary with the newly generated ``ui-assets.go`` ### Run Minio Browser with live reload diff --git a/browser/app/js/components/PolicyInput.js b/browser/app/js/components/PolicyInput.js index 9353bde41..8508fc0c4 100644 --- a/browser/app/js/components/PolicyInput.js +++ b/browser/app/js/components/PolicyInput.js @@ -7,6 +7,7 @@ import * as actions from '../actions' class PolicyInput extends Component { componentDidMount() { const {web, dispatch} = this.props + this.prefix.focus() web.ListAllBucketPolicies({ bucketName: this.props.currentBucket }).then(res => { @@ -27,8 +28,23 @@ class PolicyInput extends Component { handlePolicySubmit(e) { e.preventDefault() - const {web, dispatch} = this.props + const {web, dispatch, currentBucket} = this.props + let prefix = currentBucket + '/' + this.prefix.value + let policy = this.policy.value + + if (!prefix.endsWith('*')) prefix = prefix + '*' + + let prefixAlreadyExists = this.props.policies.some(elem => prefix === elem.prefix) + + if (prefixAlreadyExists) { + dispatch(actions.showAlert({ + type: 'danger', + message: "Policy for this prefix already exists." + })) + return + } + web.SetBucketPolicy({ bucketName: this.props.currentBucket, prefix: this.prefix.value, @@ -36,8 +52,7 @@ class PolicyInput extends Component { }) .then(() => { dispatch(actions.setPolicies([{ - policy: this.policy.value, - prefix: this.prefix.value + '*', + policy, prefix }, ...this.props.policies])) this.prefix.value = '' }) From bb292e4e38e4ce76542da557e11c4ef1780fe1a6 Mon Sep 17 00:00:00 2001 From: Krishna Srinivas Date: Wed, 10 May 2017 09:54:24 -0700 Subject: [PATCH 16/80] web-handler: Allow anonymous download of zip (#4309) fixes #4230 --- cmd/web-handlers.go | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/cmd/web-handlers.go b/cmd/web-handlers.go index eff9f3b27..17a97a727 100644 --- a/cmd/web-handlers.go +++ b/cmd/web-handlers.go @@ -21,6 +21,7 @@ import ( "encoding/json" "errors" "fmt" + "io" "io/ioutil" "net/http" "os" @@ -567,19 +568,26 @@ func (web *webAPIHandlers) DownloadZip(w http.ResponseWriter, r *http.Request) { return } - token := r.URL.Query().Get("token") - - if !isAuthTokenValid(token) { - writeWebErrorResponse(w, errAuthentication) - return - } + // Auth is done after reading the body to accommodate for anonymous requests + // when bucket policy is enabled. var args DownloadZipArgs - decodeErr := json.NewDecoder(r.Body).Decode(&args) + tenKB := 10 * 1024 // To limit r.Body to take care of misbehaving anonymous client. + decodeErr := json.NewDecoder(io.LimitReader(r.Body, int64(tenKB))).Decode(&args) if decodeErr != nil { writeWebErrorResponse(w, decodeErr) return } + token := r.URL.Query().Get("token") + if !isAuthTokenValid(token) { + for _, object := range args.Objects { + if !isBucketActionAllowed("s3:GetObject", args.BucketName, pathJoin(args.Prefix, object)) { + writeWebErrorResponse(w, errAuthentication) + return + } + } + } + archive := zip.NewWriter(w) defer archive.Close() From fa3d5d0f460e232159edc72eabdd5b26666b08a2 Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Wed, 10 May 2017 11:22:05 -0700 Subject: [PATCH 17/80] build/release: Generate sha256sums for built binaries. (#4311) We used to build sha1sum deprecate it and use sha256sum instead. Fixes #4306 --- buildscripts/build.sh | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/buildscripts/build.sh b/buildscripts/build.sh index 184408c3f..620ede972 100755 --- a/buildscripts/build.sh +++ b/buildscripts/build.sh @@ -43,8 +43,8 @@ go_build() { release_bin="$release_str/$os-$arch/$(basename $package).$release_tag" # Release binary downloadable name release_real_bin="$release_str/$os-$arch/$(basename $package)" - # Release shasum name - release_shasum="$release_str/$os-$arch/$(basename $package).shasum" + # Release sha256sum name + release_sha256sum="$release_str/$os-$arch/$(basename $package).${release_tag}.sha256sum" # Go build to build the binary. if [ "${arch}" == "arm" ]; then @@ -58,12 +58,12 @@ go_build() { ## Copy $CP -p $release_bin_6 $release_real_bin_6 - # Release shasum name - release_shasum_6="$release_str/$os-${arch}6vl/$(basename $package).shasum" + # Release sha256sum name + release_sha256sum_6="$release_str/$os-${arch}6vl/$(basename $package).${release_tag}.sha256sum" # Calculate shasum - shasum_str=$(${SHASUM} ${release_bin_6}) - echo ${shasum_str} | $SED "s/$release_str\/$os-${arch}6vl\///g" > $release_shasum_6 + shasum_str=$(${SHASUM} -a 256 ${release_bin_6}) + echo ${shasum_str} | $SED "s/$release_str\/$os-${arch}6vl\///g" > $release_sha256sum_6 # Release binary downloadable name release_real_bin_7="$release_str/$os-$arch/$(basename $package)" @@ -75,12 +75,12 @@ go_build() { ## Copy $CP -p $release_bin_7 $release_real_bin_7 - # Release shasum name - release_shasum_7="$release_str/$os-$arch/$(basename $package).shasum" + # Release sha256sum name + release_sha256sum_7="$release_str/$os-$arch/$(basename $package).${release_tag}.sha256sum" - # Calculate shasum - shasum_str=$(${SHASUM} ${release_bin_7}) - echo ${shasum_str} | $SED "s/$release_str\/$os-$arch\///g" > $release_shasum_7 + # Calculate sha256sum + shasum_str=$(${SHASUM} -a 256 ${release_bin_7}) + echo ${shasum_str} | $SED "s/$release_str\/$os-$arch\///g" > $release_sha256sum_7 else GOOS=$os GOARCH=$arch go build --ldflags "${LDFLAGS}" -o $release_bin @@ -92,8 +92,8 @@ go_build() { fi # Calculate shasum - shasum_str=$(${SHASUM} ${release_bin}) - echo ${shasum_str} | $SED "s/$release_str\/$os-$arch\///g" > $release_shasum + sha256sum_str=$(${SHASUM} -a 256 ${release_bin}) + echo ${sha256sum_str} | $SED "s/$release_str\/$os-$arch\///g" > $release_sha256sum fi } From 5a16dcf4cfe10b358cb0d07a24e86b5469885ebc Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Thu, 11 May 2017 14:27:18 -0700 Subject: [PATCH 18/80] Add a graceful msg when CTRL+C is pressed. (#4248) --- cmd/service.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/service.go b/cmd/service.go index e23a094f2..908c45676 100644 --- a/cmd/service.go +++ b/cmd/service.go @@ -102,6 +102,7 @@ func (m *ServerMux) handleServiceSignals() error { } runExitFn(nil) case serviceStop: + log.Println("Gracefully stopping... (press Ctrl+C again to force)") if err := m.Close(); err != nil { errorIf(err, "Unable to close server gracefully") } From f2ed149714e91345336e96cb1d41a7ca3b5025ba Mon Sep 17 00:00:00 2001 From: Anis Elleuch Date: Thu, 11 May 2017 23:27:32 +0200 Subject: [PATCH 19/80] Add slack channel link to corrupted disk err msg (#4270) --- cmd/fs-v1_test.go | 4 ++-- cmd/storage-errors.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/fs-v1_test.go b/cmd/fs-v1_test.go index 366ec010f..11c4dc313 100644 --- a/cmd/fs-v1_test.go +++ b/cmd/fs-v1_test.go @@ -21,6 +21,7 @@ import ( "fmt" "os" "path/filepath" + "strings" "testing" "github.com/minio/minio/pkg/lock" @@ -173,8 +174,7 @@ func TestFSMigrateObjectWithErr(t *testing.T) { } if err = initFormatFS(disk, uuid); err != nil { - if errorCause(err).Error() != - "Unable to validate 'format.json', corrupted backend format" { + if !strings.Contains(errorCause(err).Error(), "Unable to validate 'format.json', corrupted backend format") { t.Fatal("Should not fail with unexpected", err) } } diff --git a/cmd/storage-errors.go b/cmd/storage-errors.go index ee1729dce..40c7beed0 100644 --- a/cmd/storage-errors.go +++ b/cmd/storage-errors.go @@ -22,7 +22,7 @@ import "errors" var errUnexpected = errors.New("Unexpected error, please report this issue at https://github.com/minio/minio/issues") // errCorruptedFormat - corrupted backend format. -var errCorruptedFormat = errors.New("corrupted backend format") +var errCorruptedFormat = errors.New("corrupted backend format, please join https://slack.minio.io for assistance") // errUnformattedDisk - unformatted disk found. var errUnformattedDisk = errors.New("unformatted disk found") From 3bc9e6101cbd5ffddf363f5051cf560ada854e12 Mon Sep 17 00:00:00 2001 From: Aditya Manthramurthy Date: Thu, 11 May 2017 17:34:27 -0700 Subject: [PATCH 20/80] Add minimum requirements sections to notifications docs (#4328) --- docs/bucket/notifications/README.md | 45 +++++++++++++++++++---------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/docs/bucket/notifications/README.md b/docs/bucket/notifications/README.md index d4739273c..87efd7ce8 100644 --- a/docs/bucket/notifications/README.md +++ b/docs/bucket/notifications/README.md @@ -137,7 +137,7 @@ python rabbit.py ## Publish Minio events via Elasticsearch -Install [Elasticsearch](https://www.elastic.co/downloads/elasticsearch) server. Minio server supports the latest major release series 5.x. Elasticsearch provides version upgrade migration guidelines [here](https://www.elastic.co/guide/en/elasticsearch/reference/current/setup-upgrade.html). +Install [Elasticsearch](https://www.elastic.co/downloads/elasticsearch) server. This notification target supports two formats: _namespace_ and _access_. @@ -147,8 +147,11 @@ When the _access_ format is used, Minio appends events as documents in an Elasti The steps below show how to use this notification target in `namespace` format. The other format is very similar and is omitted for brevity. +### Step 1: Ensure minimum requirements are met -### Step 1: Add Elasticsearch endpoint to Minio +Minio requires a 5.x series version of Elasticsearch. This is the latest major release series. Elasticsearch provides version upgrade migration guidelines [here](https://www.elastic.co/guide/en/elasticsearch/reference/current/setup-upgrade.html). + +### Step 2: Add Elasticsearch endpoint to Minio The default location of Minio server configuration file is ``~/.minio/config.json``. The Elasticsearch configuration is located in the `elasticsearch` key under the `notify` top-level key. Create a configuration key-value pair here for your Elasticsearch instance. The key is a name for your Elasticsearch endpoint, and the value is a collection of key-value parameters described in the table below. @@ -176,7 +179,7 @@ After updating the configuration file, restart the Minio server to put the chang Note that, you can add as many Elasticsearch server endpoint configurations as needed by providing an identifier (like "1" in the example above) for the Elasticsearch instance and an object of per-server configuration parameters. -### Step 2: Enable bucket notification using Minio client +### Step 3: Enable bucket notification using Minio client We will now enable bucket event notifications on a bucket named `images`. Whenever a JPEG image is created/overwritten, a new document is added or an existing document is updated in the Elasticsearch index configured above. When an existing object is deleted, the corresponding document is deleted from the index. Thus, the rows in the Elasticsearch index, reflect the `.jpg` objects in the `images` bucket. @@ -191,7 +194,7 @@ mc events list myminio/images arn:minio:sqs:us-east-1:1:elasticsearch s3:ObjectCreated:*,s3:ObjectRemoved:* Filter: suffix=”.jpg” ``` -### Step 3: Test on Elasticsearch +### Step 4: Test on Elasticsearch Upload a JPEG image into ``images`` bucket. @@ -464,7 +467,11 @@ When the _access_ format is used, Minio appends events to a table. It creates ro The steps below show how to use this notification target in `namespace` format. The other format is very similar and is omitted for brevity. -### Step 1: Add PostgreSQL endpoint to Minio +### Step 1: Ensure minimum requirements are met + +Minio requires PostgreSQL version 9.5 or above. Minio uses the [`INSERT ON CONFLICT`](https://www.postgresql.org/docs/9.5/static/sql-insert.html#SQL-ON-CONFLICT) (aka UPSERT) feature, introduced in version 9.5 and the [JSONB](https://www.postgresql.org/docs/9.4/static/datatype-json.html) data-type introduced in version 9.4. + +### Step 2: Add PostgreSQL endpoint to Minio The default location of Minio server configuration file is ``~/.minio/config.json``. The PostgreSQL configuration is located in the `postgresql` key under the `notify` top-level key. Create a configuration key-value pair here for your PostgreSQL instance. The key is a name for your PostgreSQL endpoint, and the value is a collection of key-value parameters described in the table below. @@ -505,7 +512,7 @@ After updating the configuration file, restart the Minio server to put the chang Note that, you can add as many PostgreSQL server endpoint configurations as needed by providing an identifier (like "1" in the example above) for the PostgreSQL instance and an object of per-server configuration parameters. -### Step 2: Enable bucket notification using Minio client +### Step 3: Enable bucket notification using Minio client We will now enable bucket event notifications on a bucket named `images`. Whenever a JPEG image is created/overwritten, a new row is added or an existing row is updated in the PostgreSQL configured above. When an existing object is deleted, the corresponding row is deleted from the PostgreSQL table. Thus, the rows in the PostgreSQL table, reflect the `.jpg` objects in the `images` bucket. @@ -524,7 +531,7 @@ mc events list myminio/images arn:minio:sqs:us-east-1:1:postgresql s3:ObjectCreated:*,s3:ObjectRemoved:* Filter: suffix=”.jpg” ``` -### Step 3: Test on PostgreSQL +### Step 4: Test on PostgreSQL Open another terminal and upload a JPEG image into ``images`` bucket. @@ -547,7 +554,7 @@ key | value ## Publish Minio events via MySQL -Install MySQL from [here](https://dev.mysql.com/downloads/mysql/). To publish Minio events, you'll need `JSON` support in MySQL, available on MySQL `5.7.8` and above. We tested this setup on MySQL `5.7.17`. For illustrative purposes, we have set the root password as `password` and created a database called `miniodb` to store the events. +Install MySQL from [here](https://dev.mysql.com/downloads/mysql/). For illustrative purposes, we have set the root password as `password` and created a database called `miniodb` to store the events. This notification target supports two formats: _namespace_ and _access_. @@ -557,7 +564,11 @@ When the _access_ format is used, Minio appends events to a table. It creates ro The steps below show how to use this notification target in `namespace` format. The other format is very similar and is omitted for brevity. -### Step 1: Add MySQL server endpoint configuration to Minio +### Step 1: Ensure minimum requirements are met + +Minio requires MySQL version 5.7.8 or above. Minio uses the [JSON](https://dev.mysql.com/doc/refman/5.7/en/json.html) data-type introduced in version 5.7.8. We tested this setup on MySQL 5.7.17. + +### Step 2: Add MySQL server endpoint configuration to Minio The default location of Minio server configuration file is ``~/.minio/config.json``. The MySQL configuration is located in the `mysql` key under the `notify` top-level key. Create a configuration key-value pair here for your MySQL instance. The key is a name for your MySQL endpoint, and the value is a collection of key-value parameters described in the table below. @@ -595,7 +606,7 @@ After updating the configuration file, restart the Minio server to put the chang Note that, you can add as many MySQL server endpoint configurations as needed by providing an identifier (like "1" in the example above) for the MySQL instance and an object of per-server configuration parameters. -### Step 2: Enable bucket notification using Minio client +### Step 3: Enable bucket notification using Minio client We will now setup bucket notifications on a bucket named `images`. Whenever a JPEG image object is created/overwritten, a new row is added or an existing row is updated in the MySQL table configured above. When an existing object is deleted, the corresponding row is deleted from the MySQL table. Thus, the rows in the MySQL table, reflect the `.jpg` objects in the `images` bucket. @@ -613,7 +624,7 @@ mc events list myminio/images arn:minio:sqs:us-east-1:1:postgresql s3:ObjectCreated:*,s3:ObjectRemoved:* Filter: suffix=”.jpg” ``` -### Step 3: Test on MySQL +### Step 4: Test on MySQL Open another terminal and upload a JPEG image into ``images`` bucket: @@ -638,9 +649,13 @@ mysql> select * from minio_images; ## Publish Minio events via Kafka -Install kafka from [here](http://kafka.apache.org/). +Install Apache Kafka from [here](http://kafka.apache.org/). -### Step 1: Add kafka endpoint to Minio +### Step 1: Ensure minimum requirements are met + +Minio requires Kafka version 0.10 or 0.9. Internally Minio uses the [Shopify/sarama](https://github.com/Shopify/sarama/) library and so has the same version compatibility as provided by this library. + +### Step 2: Add Kafka endpoint to Minio The default location of Minio server configuration file is ``~/.minio/config.json``. Update the kafka configuration block in ``config.json`` as follows: @@ -656,7 +671,7 @@ The default location of Minio server configuration file is ``~/.minio/config.jso Restart Minio server to reflect config changes. ``bucketevents`` is the topic used by kafka in this example. -### Step 2: Enable bucket notification using Minio client +### Step 3: Enable bucket notification using Minio client We will enable bucket event notification to trigger whenever a JPEG image is uploaded or deleted from ``images`` bucket on ``myminio`` server. Here ARN value is ``arn:minio:sqs:us-east-1:1:kafka``. To understand more about ARN please follow [AWS ARN](http://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html) documentation. @@ -667,7 +682,7 @@ mc events list myminio/images arn:minio:sqs:us-east-1:1:kafka s3:ObjectCreated:*,s3:ObjectRemoved:* Filter: suffix=”.jpg” ``` -### Step 3: Test on kafka +### Step 4: Test on Kafka We used [kafkacat](https://github.com/edenhill/kafkacat) to print all notifications on the console. From 0404e747cf8a66e18345beb58c5439f83871b247 Mon Sep 17 00:00:00 2001 From: poornas Date: Fri, 12 May 2017 18:29:50 -0700 Subject: [PATCH 21/80] Fix broken link (#4344) --- docs/bucket/notifications/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/bucket/notifications/README.md b/docs/bucket/notifications/README.md index 87efd7ce8..d3f48a764 100644 --- a/docs/bucket/notifications/README.md +++ b/docs/bucket/notifications/README.md @@ -16,7 +16,7 @@ mechanism and can be published to the following targets: ## Prerequisites -* Install and configure Minio Server from [here](http://docs.minio.io/docs/minio). +* Install and configure Minio Server from [here](http://docs.minio.io/docs/minio-quickstart-guide). * Install and configure Minio Client from [here](https://docs.minio.io/docs/minio-client-quickstart-guide). From c63afabc9b4108f85404885155b642770ea4cae0 Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Fri, 12 May 2017 21:40:22 -0700 Subject: [PATCH 22/80] build/release: Generate sha256sums also without the release tag. (#4318) Ref #4306 --- buildscripts/build.sh | 9 +++++++++ cmd/update-main.go | 7 +++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/buildscripts/build.sh b/buildscripts/build.sh index 620ede972..15421e5db 100755 --- a/buildscripts/build.sh +++ b/buildscripts/build.sh @@ -45,6 +45,8 @@ go_build() { release_real_bin="$release_str/$os-$arch/$(basename $package)" # Release sha256sum name release_sha256sum="$release_str/$os-$arch/$(basename $package).${release_tag}.sha256sum" + # Release sha256sum default + release_sha256sum_default="$release_str/$os-$arch/$(basename $package).sha256sum" # Go build to build the binary. if [ "${arch}" == "arm" ]; then @@ -60,10 +62,13 @@ go_build() { # Release sha256sum name release_sha256sum_6="$release_str/$os-${arch}6vl/$(basename $package).${release_tag}.sha256sum" + # Release sha256sum default + release_sha256sum_default_6="$release_str/$os-${arch}6vl/$(basename $package).sha256sum" # Calculate shasum shasum_str=$(${SHASUM} -a 256 ${release_bin_6}) echo ${shasum_str} | $SED "s/$release_str\/$os-${arch}6vl\///g" > $release_sha256sum_6 + $CP -p $release_sha256sum_6 $release_sha256sum_default_6 # Release binary downloadable name release_real_bin_7="$release_str/$os-$arch/$(basename $package)" @@ -77,10 +82,13 @@ go_build() { # Release sha256sum name release_sha256sum_7="$release_str/$os-$arch/$(basename $package).${release_tag}.sha256sum" + # Release sha256sum default + release_sha256sum_default_7="$release_str/$os-${arch}/$(basename $package).sha256sum" # Calculate sha256sum shasum_str=$(${SHASUM} -a 256 ${release_bin_7}) echo ${shasum_str} | $SED "s/$release_str\/$os-$arch\///g" > $release_sha256sum_7 + $CP -p $release_sha256sum_7 $release_sha256sum_default_7 else GOOS=$os GOARCH=$arch go build --ldflags "${LDFLAGS}" -o $release_bin @@ -94,6 +102,7 @@ go_build() { # Calculate shasum sha256sum_str=$(${SHASUM} -a 256 ${release_bin}) echo ${sha256sum_str} | $SED "s/$release_str\/$os-$arch\///g" > $release_sha256sum + $CP -p $release_sha256sum $release_sha256sum_default fi } diff --git a/cmd/update-main.go b/cmd/update-main.go index 0b84da95e..b67db05e0 100644 --- a/cmd/update-main.go +++ b/cmd/update-main.go @@ -173,7 +173,6 @@ func downloadReleaseData(releaseChecksumURL string, timeout time.Duration, mode if resp.StatusCode != http.StatusOK { return data, fmt.Errorf("Error downloading URL %s. Response: %v", releaseChecksumURL, resp.Status) } - dataBytes, err := ioutil.ReadAll(resp.Body) if err != nil { return data, fmt.Errorf("Error reading response. %s", err) @@ -185,7 +184,11 @@ func downloadReleaseData(releaseChecksumURL string, timeout time.Duration, mode // DownloadReleaseData - downloads release data from minio official server. func DownloadReleaseData(timeout time.Duration, mode string) (data string, err error) { - return downloadReleaseData(minioReleaseURL+"minio.shasum", timeout, mode) + data, err = downloadReleaseData(minioReleaseURL+"minio.shasum", timeout, mode) + if err == nil { + return data, nil + } + return downloadReleaseData(minioReleaseURL+"minio.sha256sum", timeout, mode) } func parseReleaseData(data string) (releaseTime time.Time, err error) { From 155a90403ac5182626eae0f2f98fc29983fd381d Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Sun, 14 May 2017 12:05:51 -0700 Subject: [PATCH 23/80] fs/erasure: Rename meta 'md5Sum' as 'etag'. (#4319) This PR also does backend format change to 1.0.1 from 1.0.0. Backward compatible changes are still kept to read the 'md5Sum' key. But all new objects will be stored with the same details under 'etag'. Fixes #4312 --- cmd/api-headers.go | 4 +- cmd/api-response.go | 8 ++-- cmd/benchmark-utils_test.go | 44 +++++++++---------- cmd/bucket-handlers.go | 4 +- cmd/event-notifier.go | 2 +- cmd/fs-v1-metadata.go | 69 ++++++++++++++++++++---------- cmd/fs-v1-metadata_test.go | 4 +- cmd/fs-v1-multipart.go | 2 +- cmd/fs-v1.go | 20 +++++---- cmd/fs-v1_test.go | 4 +- cmd/gateway-azure-anonymous.go | 4 +- cmd/gateway-azure.go | 6 +-- cmd/gateway-handlers.go | 4 +- cmd/gateway-s3-anonymous.go | 4 +- cmd/gateway-s3.go | 6 +-- cmd/handler-utils.go | 2 +- cmd/object-api-common.go | 14 +++--- cmd/object-api-datatypes.go | 4 +- cmd/object-api-listobjects_test.go | 2 +- cmd/object-api-multipart_test.go | 2 +- cmd/object-api-putobject_test.go | 36 ++++++++-------- cmd/object-api-utils.go | 29 +++++++++++++ cmd/object-handlers-common.go | 16 +++---- cmd/object-handlers.go | 17 +++----- cmd/object_api_suite_test.go | 24 +++++------ cmd/server_test.go | 18 ++++---- cmd/web-handlers_test.go | 10 ++--- cmd/xl-v1-metadata.go | 38 +++++++++++++++- cmd/xl-v1-multipart.go | 4 +- cmd/xl-v1-object.go | 43 ++++++------------- cmd/xl-v1-utils.go | 14 ++++++ docs/backend/fs/fs.json | 1 + docs/backend/xl/xl.json | 2 +- 33 files changed, 274 insertions(+), 187 deletions(-) diff --git a/cmd/api-headers.go b/cmd/api-headers.go index cbabee1c9..9e510084b 100644 --- a/cmd/api-headers.go +++ b/cmd/api-headers.go @@ -62,8 +62,8 @@ func setObjectHeaders(w http.ResponseWriter, objInfo ObjectInfo, contentRange *h w.Header().Set("Last-Modified", lastModified) // Set Etag if available. - if objInfo.MD5Sum != "" { - w.Header().Set("ETag", "\""+objInfo.MD5Sum+"\"") + if objInfo.ETag != "" { + w.Header().Set("ETag", "\""+objInfo.ETag+"\"") } // Set all other user defined metadata. diff --git a/cmd/api-response.go b/cmd/api-response.go index bc95cab60..794178c2b 100644 --- a/cmd/api-response.go +++ b/cmd/api-response.go @@ -321,8 +321,8 @@ func generateListObjectsV1Response(bucket, prefix, marker, delimiter string, max } content.Key = object.Name content.LastModified = object.ModTime.UTC().Format(timeFormatAMZLong) - if object.MD5Sum != "" { - content.ETag = "\"" + object.MD5Sum + "\"" + if object.ETag != "" { + content.ETag = "\"" + object.ETag + "\"" } content.Size = object.Size content.StorageClass = globalMinioDefaultStorageClass @@ -370,8 +370,8 @@ func generateListObjectsV2Response(bucket, prefix, token, startAfter, delimiter } content.Key = object.Name content.LastModified = object.ModTime.UTC().Format(timeFormatAMZLong) - if object.MD5Sum != "" { - content.ETag = "\"" + object.MD5Sum + "\"" + if object.ETag != "" { + content.ETag = "\"" + object.ETag + "\"" } content.Size = object.Size content.StorageClass = globalMinioDefaultStorageClass diff --git a/cmd/benchmark-utils_test.go b/cmd/benchmark-utils_test.go index ca1c4dc41..f871c844a 100644 --- a/cmd/benchmark-utils_test.go +++ b/cmd/benchmark-utils_test.go @@ -49,7 +49,7 @@ func runPutObjectBenchmark(b *testing.B, obj ObjectLayer, objSize int) { // generate md5sum for the generated data. // md5sum of the data to written is required as input for PutObject. metadata := make(map[string]string) - metadata["md5Sum"] = getMD5Hash(textData) + metadata["etag"] = getMD5Hash(textData) sha256sum := "" // benchmark utility which helps obtain number of allocations and bytes allocated per ops. b.ReportAllocs() @@ -61,8 +61,8 @@ func runPutObjectBenchmark(b *testing.B, obj ObjectLayer, objSize int) { if err != nil { b.Fatal(err) } - if objInfo.MD5Sum != metadata["md5Sum"] { - b.Fatalf("Write no: %d: Md5Sum mismatch during object write into the bucket: Expected %s, got %s", i+1, objInfo.MD5Sum, metadata["md5Sum"]) + if objInfo.ETag != metadata["etag"] { + b.Fatalf("Write no: %d: Md5Sum mismatch during object write into the bucket: Expected %s, got %s", i+1, objInfo.ETag, metadata["etag"]) } } // Benchmark ends here. Stop timer. @@ -85,15 +85,15 @@ func runPutObjectPartBenchmark(b *testing.B, obj ObjectLayer, partSize int) { objSize := 128 * humanize.MiByte - // PutObjectPart returns md5Sum of the object inserted. - // md5Sum variable is assigned with that value. - var md5Sum, uploadID string + // PutObjectPart returns etag of the object inserted. + // etag variable is assigned with that value. + var etag, uploadID string // get text data generated for number of bytes equal to object size. textData := generateBytesData(objSize) // generate md5sum for the generated data. // md5sum of the data to written is required as input for NewMultipartUpload. metadata := make(map[string]string) - metadata["md5Sum"] = getMD5Hash(textData) + metadata["etag"] = getMD5Hash(textData) sha256sum := "" uploadID, err = obj.NewMultipartUpload(bucket, object, metadata) if err != nil { @@ -115,14 +115,14 @@ func runPutObjectPartBenchmark(b *testing.B, obj ObjectLayer, partSize int) { textPartData = textData[j*partSize:] } metadata := make(map[string]string) - metadata["md5Sum"] = getMD5Hash([]byte(textPartData)) + metadata["etag"] = getMD5Hash([]byte(textPartData)) var partInfo PartInfo - partInfo, err = obj.PutObjectPart(bucket, object, uploadID, j, int64(len(textPartData)), bytes.NewBuffer(textPartData), metadata["md5Sum"], sha256sum) + partInfo, err = obj.PutObjectPart(bucket, object, uploadID, j, int64(len(textPartData)), bytes.NewBuffer(textPartData), metadata["etag"], sha256sum) if err != nil { b.Fatal(err) } - if partInfo.ETag != metadata["md5Sum"] { - b.Fatalf("Write no: %d: Md5Sum mismatch during object write into the bucket: Expected %s, got %s", i+1, md5Sum, metadata["md5Sum"]) + if partInfo.ETag != metadata["etag"] { + b.Fatalf("Write no: %d: Md5Sum mismatch during object write into the bucket: Expected %s, got %s", i+1, etag, metadata["etag"]) } } } @@ -208,19 +208,19 @@ func runGetObjectBenchmark(b *testing.B, obj ObjectLayer, objSize int) { for i := 0; i < 10; i++ { // get text data generated for number of bytes equal to object size. textData := generateBytesData(objSize) - // generate md5sum for the generated data. - // md5sum of the data to written is required as input for PutObject. + // generate etag for the generated data. + // etag of the data to written is required as input for PutObject. // PutObject is the functions which writes the data onto the FS/XL backend. metadata := make(map[string]string) - metadata["md5Sum"] = getMD5Hash(textData) + metadata["etag"] = getMD5Hash(textData) // insert the object. var objInfo ObjectInfo objInfo, err = obj.PutObject(bucket, "object"+strconv.Itoa(i), int64(len(textData)), bytes.NewBuffer(textData), metadata, sha256sum) if err != nil { b.Fatal(err) } - if objInfo.MD5Sum != metadata["md5Sum"] { - b.Fatalf("Write no: %d: Md5Sum mismatch during object write into the bucket: Expected %s, got %s", i+1, objInfo.MD5Sum, metadata["md5Sum"]) + if objInfo.ETag != metadata["etag"] { + b.Fatalf("Write no: %d: Md5Sum mismatch during object write into the bucket: Expected %s, got %s", i+1, objInfo.ETag, metadata["etag"]) } } @@ -317,7 +317,7 @@ func runPutObjectBenchmarkParallel(b *testing.B, obj ObjectLayer, objSize int) { // generate md5sum for the generated data. // md5sum of the data to written is required as input for PutObject. metadata := make(map[string]string) - metadata["md5Sum"] = getMD5Hash([]byte(textData)) + metadata["etag"] = getMD5Hash([]byte(textData)) sha256sum := "" // benchmark utility which helps obtain number of allocations and bytes allocated per ops. b.ReportAllocs() @@ -332,8 +332,8 @@ func runPutObjectBenchmarkParallel(b *testing.B, obj ObjectLayer, objSize int) { if err != nil { b.Fatal(err) } - if objInfo.MD5Sum != metadata["md5Sum"] { - b.Fatalf("Write no: Md5Sum mismatch during object write into the bucket: Expected %s, got %s", objInfo.MD5Sum, metadata["md5Sum"]) + if objInfo.ETag != metadata["etag"] { + b.Fatalf("Write no: Md5Sum mismatch during object write into the bucket: Expected %s, got %s", objInfo.ETag, metadata["etag"]) } i++ } @@ -367,7 +367,7 @@ func runGetObjectBenchmarkParallel(b *testing.B, obj ObjectLayer, objSize int) { // md5sum of the data to written is required as input for PutObject. // PutObject is the functions which writes the data onto the FS/XL backend. metadata := make(map[string]string) - metadata["md5Sum"] = getMD5Hash([]byte(textData)) + metadata["etag"] = getMD5Hash([]byte(textData)) sha256sum := "" // insert the object. var objInfo ObjectInfo @@ -375,8 +375,8 @@ func runGetObjectBenchmarkParallel(b *testing.B, obj ObjectLayer, objSize int) { if err != nil { b.Fatal(err) } - if objInfo.MD5Sum != metadata["md5Sum"] { - b.Fatalf("Write no: %d: Md5Sum mismatch during object write into the bucket: Expected %s, got %s", i+1, objInfo.MD5Sum, metadata["md5Sum"]) + if objInfo.ETag != metadata["etag"] { + b.Fatalf("Write no: %d: Md5Sum mismatch during object write into the bucket: Expected %s, got %s", i+1, objInfo.ETag, metadata["etag"]) } } diff --git a/cmd/bucket-handlers.go b/cmd/bucket-handlers.go index 91b67c4a9..c997c1dba 100644 --- a/cmd/bucket-handlers.go +++ b/cmd/bucket-handlers.go @@ -535,7 +535,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h return } - w.Header().Set("ETag", `"`+objInfo.MD5Sum+`"`) + w.Header().Set("ETag", `"`+objInfo.ETag+`"`) w.Header().Set("Location", getObjectLocation(bucket, object)) // Get host and port from Request.RemoteAddr. @@ -568,7 +568,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h resp := encodeResponse(PostResponse{ Bucket: objInfo.Bucket, Key: objInfo.Name, - ETag: `"` + objInfo.MD5Sum + `"`, + ETag: `"` + objInfo.ETag + `"`, Location: getObjectLocation(objInfo.Bucket, objInfo.Name), }) writeResponse(w, http.StatusCreated, resp, "application/xml") diff --git a/cmd/event-notifier.go b/cmd/event-notifier.go index e8d012348..9e5003973 100644 --- a/cmd/event-notifier.go +++ b/cmd/event-notifier.go @@ -172,7 +172,7 @@ func newNotificationEvent(event eventData) NotificationEvent { // For all other events we should set ETag and Size. nEvent.S3.Object = objectMeta{ Key: escapedObj, - ETag: event.ObjInfo.MD5Sum, + ETag: event.ObjInfo.ETag, Size: event.ObjInfo.Size, ContentType: event.ObjInfo.ContentType, UserDefined: event.ObjInfo.UserDefined, diff --git a/cmd/fs-v1-metadata.go b/cmd/fs-v1-metadata.go index 4df6a8516..fdbc4eaf4 100644 --- a/cmd/fs-v1-metadata.go +++ b/cmd/fs-v1-metadata.go @@ -33,11 +33,31 @@ import ( "github.com/tidwall/gjson" ) +// FS format, and object metadata. const ( - fsMetaJSONFile = "fs.json" + // fs.json object metadata. + fsMetaJSONFile = "fs.json" + // format.json FS format metadata. fsFormatJSONFile = "format.json" ) +// FS metadata constants. +const ( + // FS backend meta 1.0.0 version. + fsMetaVersion100 = "1.0.0" + + // FS backend meta 1.0.1 version. + fsMetaVersion = "1.0.1" + + // FS backend meta format. + fsMetaFormat = "fs" + + // FS backend format version. + fsFormatVersion = fsFormatV2 + + // Add more constants here. +) + // A fsMetaV1 represents a metadata header mapping keys to sets of values. type fsMetaV1 struct { Version string `json:"version"` @@ -50,6 +70,19 @@ type fsMetaV1 struct { Parts []objectPartInfo `json:"parts,omitempty"` } +// IsValid - tells if the format is sane by validating the version +// string and format style. +func (m fsMetaV1) IsValid() bool { + return isFSMetaValid(m.Version, m.Format) +} + +// Verifies if the backend format metadata is sane by validating +// the version string and format style. +func isFSMetaValid(version, format string) bool { + return ((version == fsMetaVersion || version == fsMetaVersion100) && + format == fsMetaFormat) +} + // Converts metadata to object info. func (m fsMetaV1) ToObjectInfo(bucket, object string, fi os.FileInfo) ObjectInfo { if len(m.Meta) == 0 { @@ -78,17 +111,15 @@ func (m fsMetaV1) ToObjectInfo(bucket, object string, fi os.FileInfo) ObjectInfo objInfo.IsDir = fi.IsDir() } - objInfo.MD5Sum = m.Meta["md5Sum"] + // Extract etag from metadata. + objInfo.ETag = extractETag(m.Meta) objInfo.ContentType = m.Meta["content-type"] objInfo.ContentEncoding = m.Meta["content-encoding"] - // md5Sum has already been extracted into objInfo.MD5Sum. We - // need to remove it from m.Meta to avoid it from appearing as - // part of response headers. e.g, X-Minio-* or X-Amz-*. - delete(m.Meta, "md5Sum") - - // Save all the other userdefined API. - objInfo.UserDefined = m.Meta + // etag/md5Sum has already been extracted. We need to + // remove to avoid it from appearing as part of + // response headers. e.g, X-Minio-* or X-Amz-*. + objInfo.UserDefined = cleanMetaETag(m.Meta) // Success.. return objInfo @@ -207,6 +238,12 @@ func (m *fsMetaV1) ReadFrom(lk *lock.LockedFile) (n int64, err error) { // obtain format. m.Format = parseFSFormat(fsMetaBuf) + // Verify if the format is valid, return corrupted format + // for unrecognized formats. + if !isFSMetaValid(m.Version, m.Format) { + return 0, traceError(errCorruptedFormat) + } + // obtain metadata. m.Meta = parseFSMetaMap(fsMetaBuf) @@ -220,20 +257,6 @@ func (m *fsMetaV1) ReadFrom(lk *lock.LockedFile) (n int64, err error) { return int64(len(fsMetaBuf)), nil } -// FS metadata constants. -const ( - // FS backend meta version. - fsMetaVersion = "1.0.0" - - // FS backend meta format. - fsMetaFormat = "fs" - - // FS backend format version. - fsFormatVersion = fsFormatV2 - - // Add more constants here. -) - // FS format version strings. const ( fsFormatV1 = "1" // Previous format. diff --git a/cmd/fs-v1-metadata_test.go b/cmd/fs-v1-metadata_test.go index 620c43b5c..9d960f067 100644 --- a/cmd/fs-v1-metadata_test.go +++ b/cmd/fs-v1-metadata_test.go @@ -110,10 +110,10 @@ func TestWriteFSMetadata(t *testing.T) { if err != nil { t.Fatal("Unexpected error ", err) } - if fsMeta.Version != "1.0.0" { + if fsMeta.Version != fsMetaVersion { t.Fatalf("Unexpected version %s", fsMeta.Version) } - if fsMeta.Format != "fs" { + if fsMeta.Format != fsMetaFormat { t.Fatalf("Unexpected format %s", fsMeta.Format) } } diff --git a/cmd/fs-v1-multipart.go b/cmd/fs-v1-multipart.go index 14c3c1c12..4410fb75a 100644 --- a/cmd/fs-v1-multipart.go +++ b/cmd/fs-v1-multipart.go @@ -871,7 +871,7 @@ func (fs fsObjects) CompleteMultipartUpload(bucket string, object string, upload if len(fsMeta.Meta) == 0 { fsMeta.Meta = make(map[string]string) } - fsMeta.Meta["md5Sum"] = s3MD5 + fsMeta.Meta["etag"] = s3MD5 // Write all the set metadata. if _, err = fsMeta.WriteTo(metaFile); err != nil { diff --git a/cmd/fs-v1.go b/cmd/fs-v1.go index 5d6c64bde..4f862285a 100644 --- a/cmd/fs-v1.go +++ b/cmd/fs-v1.go @@ -712,12 +712,12 @@ func (fs fsObjects) PutObject(bucket string, object string, size int64, data io. newMD5Hex := hex.EncodeToString(md5Writer.Sum(nil)) // Update the md5sum if not set with the newly calculated one. - if len(metadata["md5Sum"]) == 0 { - metadata["md5Sum"] = newMD5Hex + if len(metadata["etag"]) == 0 { + metadata["etag"] = newMD5Hex } // md5Hex representation. - md5Hex := metadata["md5Sum"] + md5Hex := metadata["etag"] if md5Hex != "" { if newMD5Hex != md5Hex { // Returns md5 mismatch. @@ -849,8 +849,12 @@ func (fs fsObjects) getObjectETag(bucket, entry string) (string, error) { } } - fsMetaMap := parseFSMetaMap(fsMetaBuf) - return fsMetaMap["md5Sum"], nil + // Check if FS metadata is valid, if not return error. + if !isFSMetaValid(parseFSVersion(fsMetaBuf), parseFSFormat(fsMetaBuf)) { + return "", toObjectErr(traceError(errCorruptedFormat), bucket, entry) + } + + return extractETag(parseFSMetaMap(fsMetaBuf)), nil } // ListObjects - list all objects at prefix upto maxKeys., optionally delimited by '/'. Maintains the list pool @@ -901,8 +905,8 @@ func (fs fsObjects) ListObjects(bucket, prefix, marker, delimiter string, maxKey // Protect reading `fs.json`. objectLock := globalNSMutex.NewNSLock(bucket, entry) objectLock.RLock() - var md5Sum string - md5Sum, err = fs.getObjectETag(bucket, entry) + var etag string + etag, err = fs.getObjectETag(bucket, entry) objectLock.RUnlock() if err != nil { return ObjectInfo{}, err @@ -922,7 +926,7 @@ func (fs fsObjects) ListObjects(bucket, prefix, marker, delimiter string, maxKey Size: fi.Size(), ModTime: fi.ModTime(), IsDir: fi.IsDir(), - MD5Sum: md5Sum, + ETag: etag, }, nil } diff --git a/cmd/fs-v1_test.go b/cmd/fs-v1_test.go index 11c4dc313..a425ba3e4 100644 --- a/cmd/fs-v1_test.go +++ b/cmd/fs-v1_test.go @@ -244,7 +244,7 @@ func TestFSMigrateObjectWithObjects(t *testing.T) { fsPath1 := pathJoin(bucketMetaPrefix, "testvolume1", "my-object1", fsMetaJSONFile) fsPath1 = pathJoin(disk, minioMetaBucket, fsPath1) - fsMetaJSON := `{"version":"1.0.0","format":"fs","minio":{"release":"DEVELOPMENT.2017-03-27T02-26-33Z"},"meta":{"md5Sum":"467886be95c8ecfd71a2900e3f461b4f"}` + fsMetaJSON := `{"version":"1.0.0","format":"fs","minio":{"release":"DEVELOPMENT.2017-03-27T02-26-33Z"},"meta":{"etag":"467886be95c8ecfd71a2900e3f461b4f"}` if _, err = fsCreateFile(fsPath1, bytes.NewReader([]byte(fsMetaJSON)), nil, 0); err != nil { t.Fatal(err) } @@ -253,7 +253,7 @@ func TestFSMigrateObjectWithObjects(t *testing.T) { fsPath2 := pathJoin(bucketMetaPrefix, "testvolume2", "my-object2", fsMetaJSONFile) fsPath2 = pathJoin(disk, minioMetaBucket, fsPath2) - fsMetaJSON = `{"version":"1.0.0","format":"fs","minio":{"release":"DEVELOPMENT.2017-03-27T02-26-33Z"},"meta":{"md5Sum":"467886be95c8ecfd71a2900eff461b4d"}` + fsMetaJSON = `{"version":"1.0.0","format":"fs","minio":{"release":"DEVELOPMENT.2017-03-27T02-26-33Z"},"meta":{"etag":"467886be95c8ecfd71a2900eff461b4d"}` if _, err = fsCreateFile(fsPath2, bytes.NewReader([]byte(fsMetaJSON)), nil, 0); err != nil { t.Fatal(err) } diff --git a/cmd/gateway-azure-anonymous.go b/cmd/gateway-azure-anonymous.go index 69dcc1aa5..48b936343 100644 --- a/cmd/gateway-azure-anonymous.go +++ b/cmd/gateway-azure-anonymous.go @@ -127,7 +127,7 @@ func (a AzureObjects) AnonGetObjectInfo(bucket, object string) (objInfo ObjectIn 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.ETag = resp.Header.Get("Etag") objInfo.ModTime = t objInfo.Name = object objInfo.Size = contentLength @@ -182,7 +182,7 @@ func (a AzureObjects) AnonListObjects(bucket, prefix, marker, delimiter string, Name: object.Name, ModTime: t, Size: object.Properties.ContentLength, - MD5Sum: object.Properties.Etag, + ETag: object.Properties.Etag, ContentType: object.Properties.ContentType, ContentEncoding: object.Properties.ContentEncoding, }) diff --git a/cmd/gateway-azure.go b/cmd/gateway-azure.go index da9b74630..22082b11c 100644 --- a/cmd/gateway-azure.go +++ b/cmd/gateway-azure.go @@ -235,7 +235,7 @@ func (a AzureObjects) ListObjects(bucket, prefix, marker, delimiter string, maxK Name: object.Name, ModTime: t, Size: object.Properties.ContentLength, - MD5Sum: canonicalizeETag(object.Properties.Etag), + ETag: canonicalizeETag(object.Properties.Etag), ContentType: object.Properties.ContentType, ContentEncoding: object.Properties.ContentEncoding, }) @@ -285,7 +285,7 @@ func (a AzureObjects) GetObjectInfo(bucket, object string) (objInfo ObjectInfo, objInfo = ObjectInfo{ Bucket: bucket, UserDefined: make(map[string]string), - MD5Sum: canonicalizeETag(prop.Etag), + ETag: canonicalizeETag(prop.Etag), ModTime: t, Name: object, Size: prop.ContentLength, @@ -319,7 +319,7 @@ func (a AzureObjects) PutObject(bucket, object string, size int64, data io.Reade teeReader = io.TeeReader(data, sha256Writer) } - delete(metadata, "md5Sum") + delete(metadata, "etag") err = a.client.CreateBlockBlobFromReader(bucket, object, uint64(size), teeReader, canonicalMetadata(metadata)) if err != nil { diff --git a/cmd/gateway-handlers.go b/cmd/gateway-handlers.go index bce48a8dd..456b4446d 100644 --- a/cmd/gateway-handlers.go +++ b/cmd/gateway-handlers.go @@ -234,7 +234,7 @@ func (api gatewayAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Re } // Make sure we hex encode md5sum here. - metadata["md5Sum"] = hex.EncodeToString(md5Bytes) + metadata["etag"] = hex.EncodeToString(md5Bytes) sha256sum := "" @@ -282,7 +282,7 @@ func (api gatewayAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Re return } - w.Header().Set("ETag", "\""+objInfo.MD5Sum+"\"") + w.Header().Set("ETag", "\""+objInfo.ETag+"\"") writeSuccessResponseHeadersOnly(w) } diff --git a/cmd/gateway-s3-anonymous.go b/cmd/gateway-s3-anonymous.go index d4fc4e31a..4fbb1b716 100644 --- a/cmd/gateway-s3-anonymous.go +++ b/cmd/gateway-s3-anonymous.go @@ -36,13 +36,13 @@ func (l *s3Gateway) AnonPutObject(bucket string, object string, size int64, data } var md5sumBytes []byte - md5sum := metadata["md5Sum"] + md5sum := metadata["etag"] if md5sum != "" { md5sumBytes, err = hex.DecodeString(md5sum) if err != nil { return ObjectInfo{}, s3ToObjectError(traceError(err), bucket, object) } - delete(metadata, "md5Sum") + delete(metadata, "etag") } oi, err := l.anonClient.PutObject(bucket, object, size, data, md5sumBytes, sha256sumBytes, toMinioClientMetadata(metadata)) diff --git a/cmd/gateway-s3.go b/cmd/gateway-s3.go index f2be3f269..2a09d5fc7 100644 --- a/cmd/gateway-s3.go +++ b/cmd/gateway-s3.go @@ -295,7 +295,7 @@ func fromMinioClientObjectInfo(bucket string, oi minio.ObjectInfo) ObjectInfo { Name: oi.Key, ModTime: oi.LastModified, Size: oi.Size, - MD5Sum: oi.ETag, + ETag: oi.ETag, UserDefined: userDefined, ContentType: oi.ContentType, ContentEncoding: oi.Metadata.Get("Content-Encoding"), @@ -326,13 +326,13 @@ func (l *s3Gateway) PutObject(bucket string, object string, size int64, data io. } var md5sumBytes []byte - md5sum := metadata["md5Sum"] + md5sum := metadata["etag"] if md5sum != "" { md5sumBytes, err = hex.DecodeString(md5sum) if err != nil { return ObjectInfo{}, s3ToObjectError(traceError(err), bucket, object) } - delete(metadata, "md5Sum") + delete(metadata, "etag") } oi, err := l.Client.PutObject(bucket, object, size, data, md5sumBytes, sha256sumBytes, toMinioClientMetadata(metadata)) diff --git a/cmd/handler-utils.go b/cmd/handler-utils.go index e6c9dc35b..46b1d4158 100644 --- a/cmd/handler-utils.go +++ b/cmd/handler-utils.go @@ -134,7 +134,7 @@ func getRedirectPostRawQuery(objInfo ObjectInfo) string { redirectValues := make(url.Values) redirectValues.Set("bucket", objInfo.Bucket) redirectValues.Set("key", objInfo.Name) - redirectValues.Set("etag", "\""+objInfo.MD5Sum+"\"") + redirectValues.Set("etag", "\""+objInfo.ETag+"\"") return redirectValues.Encode() } diff --git a/cmd/object-api-common.go b/cmd/object-api-common.go index b2056f01d..506a91ec7 100644 --- a/cmd/object-api-common.go +++ b/cmd/object-api-common.go @@ -35,8 +35,8 @@ const ( // Objects meta prefix. objectMetaPrefix = "objects" - // Md5Sum of empty string. - emptyStrMd5Sum = "d41d8cd98f00b204e9800998ecf8427e" + // ETag (hex encoded md5sum) of empty string. + emptyETag = "d41d8cd98f00b204e9800998ecf8427e" ) // Global object layer mutex, used for safely updating object layer. @@ -68,10 +68,10 @@ func dirObjectInfo(bucket, object string, size int64, metadata map[string]string // This is a special case with size as '0' and object ends with // a slash separator, we treat it like a valid operation and // return success. - md5Sum := metadata["md5Sum"] - delete(metadata, "md5Sum") - if md5Sum == "" { - md5Sum = emptyStrMd5Sum + etag := metadata["etag"] + delete(metadata, "etag") + if etag == "" { + etag = emptyETag } return ObjectInfo{ @@ -81,7 +81,7 @@ func dirObjectInfo(bucket, object string, size int64, metadata map[string]string ContentType: "application/octet-stream", IsDir: true, Size: size, - MD5Sum: md5Sum, + ETag: etag, UserDefined: metadata, } } diff --git a/cmd/object-api-datatypes.go b/cmd/object-api-datatypes.go index 4561b82bd..7ebc1644c 100644 --- a/cmd/object-api-datatypes.go +++ b/cmd/object-api-datatypes.go @@ -101,8 +101,8 @@ type ObjectInfo struct { // IsDir indicates if the object is prefix. IsDir bool - // Hex encoded md5 checksum of the object. - MD5Sum string + // Hex encoded unique entity tag of the object. + ETag string // A standard MIME type describing the format of the object. ContentType string diff --git a/cmd/object-api-listobjects_test.go b/cmd/object-api-listobjects_test.go index 9d469f766..560a36101 100644 --- a/cmd/object-api-listobjects_test.go +++ b/cmd/object-api-listobjects_test.go @@ -549,7 +549,7 @@ func testListObjects(obj ObjectLayer, instanceType string, t TestErrHandler) { if testCase.result.Objects[j].Name != result.Objects[j].Name { t.Errorf("Test %d: %s: Expected object name to be \"%s\", but found \"%s\" instead", i+1, instanceType, testCase.result.Objects[j].Name, result.Objects[j].Name) } - if result.Objects[j].MD5Sum == "" { + if result.Objects[j].ETag == "" { t.Errorf("Test %d: %s: Expected md5sum to be not empty, but found empty instead", i+1, instanceType) } diff --git a/cmd/object-api-multipart_test.go b/cmd/object-api-multipart_test.go index b78bb8c7d..263bb36ae 100644 --- a/cmd/object-api-multipart_test.go +++ b/cmd/object-api-multipart_test.go @@ -1930,7 +1930,7 @@ func testObjectCompleteMultipartUpload(obj ObjectLayer, instanceType string, t T if actualErr == nil && testCase.shouldPass { // Asserting IsTruncated. - if actualResult.MD5Sum != testCase.expectedS3MD5 { + if actualResult.ETag != testCase.expectedS3MD5 { t.Errorf("Test %d: %s: Expected the result to be \"%v\", but found it to \"%v\"", i+1, instanceType, testCase.expectedS3MD5, actualResult) } } diff --git a/cmd/object-api-putobject_test.go b/cmd/object-api-putobject_test.go index 3e889bfb9..4a8e702ce 100644 --- a/cmd/object-api-putobject_test.go +++ b/cmd/object-api-putobject_test.go @@ -29,7 +29,7 @@ import ( ) func md5Header(data []byte) map[string]string { - return map[string]string{"md5Sum": getMD5Hash([]byte(data))} + return map[string]string{"etag": getMD5Hash([]byte(data))} } // Wrapper for calling PutObject tests for both XL multiple disks and single node setup. @@ -94,29 +94,29 @@ func testObjectAPIPutObject(obj ObjectLayer, instanceType string, t TestErrHandl // Test case - 7. // Input to replicate Md5 mismatch. - {bucket, object, []byte(""), map[string]string{"md5Sum": "a35"}, "", 0, "", + {bucket, object, []byte(""), map[string]string{"etag": "a35"}, "", 0, "", BadDigest{ExpectedMD5: "a35", CalculatedMD5: "d41d8cd98f00b204e9800998ecf8427e"}}, // Test case - 8. // With incorrect sha256. - {bucket, object, []byte("abcd"), map[string]string{"md5Sum": "e2fc714c4727ee9395f324cd2e7f331f"}, "incorrect-sha256", int64(len("abcd")), "", SHA256Mismatch{}}, + {bucket, object, []byte("abcd"), map[string]string{"etag": "e2fc714c4727ee9395f324cd2e7f331f"}, "incorrect-sha256", int64(len("abcd")), "", SHA256Mismatch{}}, // Test case - 9. // Input with size more than the size of actual data inside the reader. - {bucket, object, []byte("abcd"), map[string]string{"md5Sum": "a35"}, "", int64(len("abcd") + 1), "", + {bucket, object, []byte("abcd"), map[string]string{"etag": "a35"}, "", int64(len("abcd") + 1), "", IncompleteBody{}}, // Test case - 10. // Input with size less than the size of actual data inside the reader. - {bucket, object, []byte("abcd"), map[string]string{"md5Sum": "a35"}, "", int64(len("abcd") - 1), "", + {bucket, object, []byte("abcd"), map[string]string{"etag": "a35"}, "", int64(len("abcd") - 1), "", BadDigest{ExpectedMD5: "a35", CalculatedMD5: "900150983cd24fb0d6963f7d28e17f72"}}, // Test case - 11-14. // Validating for success cases. - {bucket, object, []byte("abcd"), map[string]string{"md5Sum": "e2fc714c4727ee9395f324cd2e7f331f"}, "", int64(len("abcd")), "", nil}, - {bucket, object, []byte("efgh"), map[string]string{"md5Sum": "1f7690ebdd9b4caf8fab49ca1757bf27"}, "", int64(len("efgh")), "", nil}, - {bucket, object, []byte("ijkl"), map[string]string{"md5Sum": "09a0877d04abf8759f99adec02baf579"}, "", int64(len("ijkl")), "", nil}, - {bucket, object, []byte("mnop"), map[string]string{"md5Sum": "e132e96a5ddad6da8b07bba6f6131fef"}, "", int64(len("mnop")), "", nil}, + {bucket, object, []byte("abcd"), map[string]string{"etag": "e2fc714c4727ee9395f324cd2e7f331f"}, "", int64(len("abcd")), "", nil}, + {bucket, object, []byte("efgh"), map[string]string{"etag": "1f7690ebdd9b4caf8fab49ca1757bf27"}, "", int64(len("efgh")), "", nil}, + {bucket, object, []byte("ijkl"), map[string]string{"etag": "09a0877d04abf8759f99adec02baf579"}, "", int64(len("ijkl")), "", nil}, + {bucket, object, []byte("mnop"), map[string]string{"etag": "e132e96a5ddad6da8b07bba6f6131fef"}, "", int64(len("mnop")), "", nil}, // Test case 15-17. // With no metadata @@ -169,8 +169,8 @@ func testObjectAPIPutObject(obj ObjectLayer, instanceType string, t TestErrHandl // Test passes as expected, but the output values are verified for correctness here. if actualErr == nil { // Asserting whether the md5 output is correct. - if expectedMD5, ok := testCase.inputMeta["md5Sum"]; ok && expectedMD5 != objInfo.MD5Sum { - t.Errorf("Test %d: %s: Calculated Md5 different from the actual one %s.", i+1, instanceType, objInfo.MD5Sum) + if expectedMD5, ok := testCase.inputMeta["etag"]; ok && expectedMD5 != objInfo.ETag { + t.Errorf("Test %d: %s: Calculated Md5 different from the actual one %s.", i+1, instanceType, objInfo.ETag) } } } @@ -220,10 +220,10 @@ func testObjectAPIPutObjectDiskNotFound(obj ObjectLayer, instanceType string, di expectedError error }{ // Validating for success cases. - {bucket, object, []byte("abcd"), map[string]string{"md5Sum": "e2fc714c4727ee9395f324cd2e7f331f"}, int64(len("abcd")), true, "", nil}, - {bucket, object, []byte("efgh"), map[string]string{"md5Sum": "1f7690ebdd9b4caf8fab49ca1757bf27"}, int64(len("efgh")), true, "", nil}, - {bucket, object, []byte("ijkl"), map[string]string{"md5Sum": "09a0877d04abf8759f99adec02baf579"}, int64(len("ijkl")), true, "", nil}, - {bucket, object, []byte("mnop"), map[string]string{"md5Sum": "e132e96a5ddad6da8b07bba6f6131fef"}, int64(len("mnop")), true, "", nil}, + {bucket, object, []byte("abcd"), map[string]string{"etag": "e2fc714c4727ee9395f324cd2e7f331f"}, int64(len("abcd")), true, "", nil}, + {bucket, object, []byte("efgh"), map[string]string{"etag": "1f7690ebdd9b4caf8fab49ca1757bf27"}, int64(len("efgh")), true, "", nil}, + {bucket, object, []byte("ijkl"), map[string]string{"etag": "09a0877d04abf8759f99adec02baf579"}, int64(len("ijkl")), true, "", nil}, + {bucket, object, []byte("mnop"), map[string]string{"etag": "e132e96a5ddad6da8b07bba6f6131fef"}, int64(len("mnop")), true, "", nil}, } sha256sum := "" @@ -246,8 +246,8 @@ func testObjectAPIPutObjectDiskNotFound(obj ObjectLayer, instanceType string, di // Test passes as expected, but the output values are verified for correctness here. if actualErr == nil && testCase.shouldPass { // Asserting whether the md5 output is correct. - if testCase.inputMeta["md5Sum"] != objInfo.MD5Sum { - t.Errorf("Test %d: %s: Calculated Md5 different from the actual one %s.", i+1, instanceType, objInfo.MD5Sum) + if testCase.inputMeta["etag"] != objInfo.ETag { + t.Errorf("Test %d: %s: Calculated Md5 different from the actual one %s.", i+1, instanceType, objInfo.ETag) } } } @@ -271,7 +271,7 @@ func testObjectAPIPutObjectDiskNotFound(obj ObjectLayer, instanceType string, di bucket, object, []byte("mnop"), - map[string]string{"md5Sum": "e132e96a5ddad6da8b07bba6f6131fef"}, + map[string]string{"etag": "e132e96a5ddad6da8b07bba6f6131fef"}, int64(len("mnop")), false, "", diff --git a/cmd/object-api-utils.go b/cmd/object-api-utils.go index acb14f508..ec95c3250 100644 --- a/cmd/object-api-utils.go +++ b/cmd/object-api-utils.go @@ -187,6 +187,35 @@ func getCompleteMultipartMD5(parts []completePart) (string, error) { return s3MD5, nil } +// Clean meta etag keys 'md5Sum', 'etag'. +func cleanMetaETag(metadata map[string]string) map[string]string { + return cleanMetadata(metadata, "md5Sum", "etag") +} + +// Clean metadata takes keys to be filtered +// and returns a new map with the keys filtered. +func cleanMetadata(metadata map[string]string, keyNames ...string) map[string]string { + var newMeta = make(map[string]string) + for k, v := range metadata { + if contains(keyNames, k) { + continue + } + newMeta[k] = v + } + return newMeta +} + +// Extracts etag value from the metadata. +func extractETag(metadata map[string]string) string { + // md5Sum tag is kept for backward compatibility. + etag, ok := metadata["md5Sum"] + if !ok { + etag = metadata["etag"] + } + // Success. + return etag +} + // Prefix matcher string matches prefix in a platform specific way. // For example on windows since its case insensitive we are supposed // to do case insensitive checks. diff --git a/cmd/object-handlers-common.go b/cmd/object-handlers-common.go index 01f2ba2ae..68bfcb8be 100644 --- a/cmd/object-handlers-common.go +++ b/cmd/object-handlers-common.go @@ -59,8 +59,8 @@ func checkCopyObjectPreconditions(w http.ResponseWriter, r *http.Request, objInf // set object-related metadata headers w.Header().Set("Last-Modified", objInfo.ModTime.UTC().Format(http.TimeFormat)) - if objInfo.MD5Sum != "" { - w.Header().Set("ETag", "\""+objInfo.MD5Sum+"\"") + if objInfo.ETag != "" { + w.Header().Set("ETag", "\""+objInfo.ETag+"\"") } } // x-amz-copy-source-if-modified-since: Return the object only if it has been modified @@ -95,7 +95,7 @@ func checkCopyObjectPreconditions(w http.ResponseWriter, r *http.Request, objInf // same as the one specified; otherwise return a 412 (precondition failed). ifMatchETagHeader := r.Header.Get("x-amz-copy-source-if-match") if ifMatchETagHeader != "" { - if objInfo.MD5Sum != "" && !isETagEqual(objInfo.MD5Sum, ifMatchETagHeader) { + if objInfo.ETag != "" && !isETagEqual(objInfo.ETag, ifMatchETagHeader) { // If the object ETag does not match with the specified ETag. writeHeaders() writeErrorResponse(w, ErrPreconditionFailed, r.URL) @@ -107,7 +107,7 @@ func checkCopyObjectPreconditions(w http.ResponseWriter, r *http.Request, objInf // one specified otherwise, return a 304 (not modified). ifNoneMatchETagHeader := r.Header.Get("x-amz-copy-source-if-none-match") if ifNoneMatchETagHeader != "" { - if objInfo.MD5Sum != "" && isETagEqual(objInfo.MD5Sum, ifNoneMatchETagHeader) { + if objInfo.ETag != "" && isETagEqual(objInfo.ETag, ifNoneMatchETagHeader) { // If the object ETag matches with the specified ETag. writeHeaders() writeErrorResponse(w, ErrPreconditionFailed, r.URL) @@ -144,8 +144,8 @@ func checkPreconditions(w http.ResponseWriter, r *http.Request, objInfo ObjectIn // set object-related metadata headers w.Header().Set("Last-Modified", objInfo.ModTime.UTC().Format(http.TimeFormat)) - if objInfo.MD5Sum != "" { - w.Header().Set("ETag", "\""+objInfo.MD5Sum+"\"") + if objInfo.ETag != "" { + w.Header().Set("ETag", "\""+objInfo.ETag+"\"") } } // If-Modified-Since : Return the object only if it has been modified since the specified time, @@ -180,7 +180,7 @@ func checkPreconditions(w http.ResponseWriter, r *http.Request, objInfo ObjectIn // otherwise return a 412 (precondition failed). ifMatchETagHeader := r.Header.Get("If-Match") if ifMatchETagHeader != "" { - if !isETagEqual(objInfo.MD5Sum, ifMatchETagHeader) { + if !isETagEqual(objInfo.ETag, ifMatchETagHeader) { // If the object ETag does not match with the specified ETag. writeHeaders() writeErrorResponse(w, ErrPreconditionFailed, r.URL) @@ -192,7 +192,7 @@ func checkPreconditions(w http.ResponseWriter, r *http.Request, objInfo ObjectIn // one specified otherwise, return a 304 (not modified). ifNoneMatchETagHeader := r.Header.Get("If-None-Match") if ifNoneMatchETagHeader != "" { - if isETagEqual(objInfo.MD5Sum, ifNoneMatchETagHeader) { + if isETagEqual(objInfo.ETag, ifNoneMatchETagHeader) { // If the object ETag matches with the specified ETag. writeHeaders() w.WriteHeader(http.StatusNotModified) diff --git a/cmd/object-handlers.go b/cmd/object-handlers.go index 108a6139e..bc432a097 100644 --- a/cmd/object-handlers.go +++ b/cmd/object-handlers.go @@ -360,10 +360,8 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re defaultMeta := objInfo.UserDefined - // Make sure to remove saved md5sum, object might have been uploaded - // as multipart which doesn't have a standard md5sum, we just let - // CopyObject calculate a new one. - delete(defaultMeta, "md5Sum") + // Make sure to remove saved etag, CopyObject calculates a new one. + delete(defaultMeta, "etag") newMetadata := getCpObjMetadataFromHeader(r.Header, defaultMeta) // Check if x-amz-metadata-directive was not set to REPLACE and source, @@ -383,8 +381,7 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re return } - md5Sum := objInfo.MD5Sum - response := generateCopyObjectResponse(md5Sum, objInfo.ModTime) + response := generateCopyObjectResponse(objInfo.ETag, objInfo.ModTime) encodedSuccessResponse := encodeResponse(response) // Write success response. @@ -482,7 +479,7 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req } // Make sure we hex encode md5sum here. - metadata["md5Sum"] = hex.EncodeToString(md5Bytes) + metadata["etag"] = hex.EncodeToString(md5Bytes) sha256sum := "" @@ -540,7 +537,7 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req writeErrorResponse(w, toAPIErrorCode(err), r.URL) return } - w.Header().Set("ETag", "\""+objInfo.MD5Sum+"\"") + w.Header().Set("ETag", "\""+objInfo.ETag+"\"") writeSuccessResponseHeadersOnly(w) // Get host and port from Request.RemoteAddr. @@ -965,7 +962,7 @@ func (api objectAPIHandlers) CompleteMultipartUploadHandler(w http.ResponseWrite // Get object location. location := getLocation(r) // Generate complete multipart response. - response := generateCompleteMultpartUploadResponse(bucket, object, location, objInfo.MD5Sum) + response := generateCompleteMultpartUploadResponse(bucket, object, location, objInfo.ETag) encodedSuccessResponse := encodeResponse(response) if err != nil { errorIf(err, "Unable to parse CompleteMultipartUpload response") @@ -974,7 +971,7 @@ func (api objectAPIHandlers) CompleteMultipartUploadHandler(w http.ResponseWrite } // Set etag. - w.Header().Set("ETag", "\""+objInfo.MD5Sum+"\"") + w.Header().Set("ETag", "\""+objInfo.ETag+"\"") // Write success response. writeSuccessResponseXML(w, encodedSuccessResponse) diff --git a/cmd/object_api_suite_test.go b/cmd/object_api_suite_test.go index 3f0af2d25..4fe4661ca 100644 --- a/cmd/object_api_suite_test.go +++ b/cmd/object_api_suite_test.go @@ -104,14 +104,14 @@ func testMultipartObjectCreation(obj ObjectLayer, instanceType string, c TestErr data := bytes.Repeat([]byte("0123456789abcdef"), 5*humanize.MiByte/16) completedParts := completeMultipartUpload{} for i := 1; i <= 10; i++ { - expectedMD5Sumhex := getMD5Hash(data) + expectedETaghex := getMD5Hash(data) var calcPartInfo PartInfo - calcPartInfo, err = obj.PutObjectPart("bucket", "key", uploadID, i, int64(len(data)), bytes.NewBuffer(data), expectedMD5Sumhex, "") + calcPartInfo, err = obj.PutObjectPart("bucket", "key", uploadID, i, int64(len(data)), bytes.NewBuffer(data), expectedETaghex, "") if err != nil { c.Errorf("%s: %s", instanceType, err) } - if calcPartInfo.ETag != expectedMD5Sumhex { + if calcPartInfo.ETag != expectedETaghex { c.Errorf("MD5 Mismatch") } completedParts.Parts = append(completedParts.Parts, completePart{ @@ -123,7 +123,7 @@ func testMultipartObjectCreation(obj ObjectLayer, instanceType string, c TestErr if err != nil { c.Fatalf("%s: %s", instanceType, err) } - if objInfo.MD5Sum != "7d364cb728ce42a74a96d22949beefb2-10" { + if objInfo.ETag != "7d364cb728ce42a74a96d22949beefb2-10" { c.Errorf("Md5 mismtch") } } @@ -153,18 +153,18 @@ func testMultipartObjectAbort(obj ObjectLayer, instanceType string, c TestErrHan randomString = randomString + strconv.Itoa(num) } - expectedMD5Sumhex := getMD5Hash([]byte(randomString)) + expectedETaghex := getMD5Hash([]byte(randomString)) - metadata["md5"] = expectedMD5Sumhex + metadata["md5"] = expectedETaghex var calcPartInfo PartInfo - calcPartInfo, err = obj.PutObjectPart("bucket", "key", uploadID, i, int64(len(randomString)), bytes.NewBufferString(randomString), expectedMD5Sumhex, "") + calcPartInfo, err = obj.PutObjectPart("bucket", "key", uploadID, i, int64(len(randomString)), bytes.NewBufferString(randomString), expectedETaghex, "") if err != nil { c.Fatalf("%s: %s", instanceType, err) } - if calcPartInfo.ETag != expectedMD5Sumhex { + if calcPartInfo.ETag != expectedETaghex { c.Errorf("Md5 Mismatch") } - parts[i] = expectedMD5Sumhex + parts[i] = expectedETaghex } err = obj.AbortMultipartUpload("bucket", "key", uploadID) if err != nil { @@ -191,18 +191,18 @@ func testMultipleObjectCreation(obj ObjectLayer, instanceType string, c TestErrH randomString = randomString + strconv.Itoa(num) } - expectedMD5Sumhex := getMD5Hash([]byte(randomString)) + expectedETaghex := getMD5Hash([]byte(randomString)) key := "obj" + strconv.Itoa(i) objects[key] = []byte(randomString) metadata := make(map[string]string) - metadata["md5Sum"] = expectedMD5Sumhex + metadata["etag"] = expectedETaghex var objInfo ObjectInfo objInfo, err = obj.PutObject("bucket", key, int64(len(randomString)), bytes.NewBufferString(randomString), metadata, "") if err != nil { c.Fatalf("%s: %s", instanceType, err) } - if objInfo.MD5Sum != expectedMD5Sumhex { + if objInfo.ETag != expectedETaghex { c.Errorf("Md5 Mismatch") } } diff --git a/cmd/server_test.go b/cmd/server_test.go index a3222d499..8f607c142 100644 --- a/cmd/server_test.go +++ b/cmd/server_test.go @@ -1151,7 +1151,7 @@ func (s *TestSuiteCommon) TestPutObject(c *C) { c.Assert(err, IsNil) c.Assert(response.StatusCode, Equals, http.StatusOK) // The response Etag header should contain Md5sum of an empty string. - c.Assert(response.Header.Get("Etag"), Equals, "\""+emptyStrMd5Sum+"\"") + c.Assert(response.Header.Get("Etag"), Equals, "\""+emptyETag+"\"") } // TestListBuckets - Make request for listing of all buckets. @@ -1841,7 +1841,7 @@ func (s *TestSuiteCommon) TestGetObjectLarge11MiB(c *C) { getContent, err := ioutil.ReadAll(response.Body) c.Assert(err, IsNil) - // Get md5Sum of the response content. + // Get etag of the response content. getMD5 := getMD5Hash(getContent) // Compare putContent and getContent. @@ -2505,8 +2505,8 @@ func (s *TestSuiteCommon) TestObjectValidMD5(c *C) { // Create a byte array of 5MB. // content for the object to be uploaded. data := bytes.Repeat([]byte("0123456789abcdef"), 5*humanize.MiByte/16) - // calculate md5Sum of the data. - md5SumBase64 := getMD5HashBase64(data) + // calculate etag of the data. + etagBase64 := getMD5HashBase64(data) buffer1 := bytes.NewReader(data) objectName := "test-1-object" @@ -2515,7 +2515,7 @@ func (s *TestSuiteCommon) TestObjectValidMD5(c *C) { int64(buffer1.Len()), buffer1, s.accessKey, s.secretKey, s.signer) c.Assert(err, IsNil) // set the Content-Md5 to be the hash to content. - request.Header.Set("Content-Md5", md5SumBase64) + request.Header.Set("Content-Md5", etagBase64) client = http.Client{Transport: s.transport} response, err = client.Do(request) c.Assert(err, IsNil) @@ -2578,14 +2578,14 @@ func (s *TestSuiteCommon) TestObjectMultipart(c *C) { // content for the part to be uploaded. // Create a byte array of 5MB. data := bytes.Repeat([]byte("0123456789abcdef"), 5*humanize.MiByte/16) - // calculate md5Sum of the data. + // calculate etag of the data. md5SumBase64 := getMD5HashBase64(data) buffer1 := bytes.NewReader(data) // HTTP request for the part to be uploaded. request, err = newTestSignedRequest("PUT", getPartUploadURL(s.endPoint, bucketName, objectName, uploadID, "1"), int64(buffer1.Len()), buffer1, s.accessKey, s.secretKey, s.signer) - // set the Content-Md5 header to the base64 encoding the md5Sum of the content. + // set the Content-Md5 header to the base64 encoding the etag of the content. request.Header.Set("Content-Md5", md5SumBase64) c.Assert(err, IsNil) @@ -2599,14 +2599,14 @@ func (s *TestSuiteCommon) TestObjectMultipart(c *C) { // Create a byte array of 1 byte. data = []byte("0") - // calculate md5Sum of the data. + // calculate etag of the data. md5SumBase64 = getMD5HashBase64(data) buffer2 := bytes.NewReader(data) // HTTP request for the second part to be uploaded. request, err = newTestSignedRequest("PUT", getPartUploadURL(s.endPoint, bucketName, objectName, uploadID, "2"), int64(buffer2.Len()), buffer2, s.accessKey, s.secretKey, s.signer) - // set the Content-Md5 header to the base64 encoding the md5Sum of the content. + // set the Content-Md5 header to the base64 encoding the etag of the content. request.Header.Set("Content-Md5", md5SumBase64) c.Assert(err, IsNil) diff --git a/cmd/web-handlers_test.go b/cmd/web-handlers_test.go index 2520d20c5..4120aa4c0 100644 --- a/cmd/web-handlers_test.go +++ b/cmd/web-handlers_test.go @@ -370,7 +370,7 @@ func testListObjectsWebHandler(obj ObjectLayer, instanceType string, t TestErrHa data := bytes.Repeat([]byte("a"), objectSize) - _, err = obj.PutObject(bucketName, objectName, int64(len(data)), bytes.NewReader(data), map[string]string{"md5Sum": "c9a34cfc85d982698c6ac89f76071abd"}, "") + _, err = obj.PutObject(bucketName, objectName, int64(len(data)), bytes.NewReader(data), map[string]string{"etag": "c9a34cfc85d982698c6ac89f76071abd"}, "") if err != nil { t.Fatalf("Was not able to upload an object, %v", err) @@ -465,14 +465,14 @@ func testRemoveObjectWebHandler(obj ObjectLayer, instanceType string, t TestErrH data := bytes.Repeat([]byte("a"), objectSize) _, err = obj.PutObject(bucketName, objectName, int64(len(data)), bytes.NewReader(data), - map[string]string{"md5Sum": "c9a34cfc85d982698c6ac89f76071abd"}, "") + map[string]string{"etag": "c9a34cfc85d982698c6ac89f76071abd"}, "") if err != nil { t.Fatalf("Was not able to upload an object, %v", err) } objectName = "a/object" _, err = obj.PutObject(bucketName, objectName, int64(len(data)), bytes.NewReader(data), - map[string]string{"md5Sum": "c9a34cfc85d982698c6ac89f76071abd"}, "") + map[string]string{"etag": "c9a34cfc85d982698c6ac89f76071abd"}, "") if err != nil { t.Fatalf("Was not able to upload an object, %v", err) } @@ -788,7 +788,7 @@ func testDownloadWebHandler(obj ObjectLayer, instanceType string, t TestErrHandl } content := []byte("temporary file's content") - _, err = obj.PutObject(bucketName, objectName, int64(len(content)), bytes.NewReader(content), map[string]string{"md5Sum": "01ce59706106fe5e02e7f55fffda7f34"}, "") + _, err = obj.PutObject(bucketName, objectName, int64(len(content)), bytes.NewReader(content), map[string]string{"etag": "01ce59706106fe5e02e7f55fffda7f34"}, "") if err != nil { t.Fatalf("Was not able to upload an object, %v", err) } @@ -940,7 +940,7 @@ func testWebPresignedGetHandler(obj ObjectLayer, instanceType string, t TestErrH } data := bytes.Repeat([]byte("a"), objectSize) - _, err = obj.PutObject(bucketName, objectName, int64(len(data)), bytes.NewReader(data), map[string]string{"md5Sum": "c9a34cfc85d982698c6ac89f76071abd"}, "") + _, err = obj.PutObject(bucketName, objectName, int64(len(data)), bytes.NewReader(data), map[string]string{"etag": "c9a34cfc85d982698c6ac89f76071abd"}, "") if err != nil { t.Fatalf("Was not able to upload an object, %v", err) } diff --git a/cmd/xl-v1-metadata.go b/cmd/xl-v1-metadata.go index cc3647598..429cc8f8e 100644 --- a/cmd/xl-v1-metadata.go +++ b/cmd/xl-v1-metadata.go @@ -146,7 +146,10 @@ type xlMetaV1 struct { // XL metadata constants. const ( // XL meta version. - xlMetaVersion = "1.0.0" + xlMetaVersion = "1.0.1" + + // XL meta version. + xlMetaVersion100 = "1.0.0" // XL meta format string. xlMetaFormat = "xl" @@ -173,7 +176,38 @@ func newXLMetaV1(object string, dataBlocks, parityBlocks int) (xlMeta xlMetaV1) // IsValid - tells if the format is sane by validating the version // string and format style. func (m xlMetaV1) IsValid() bool { - return m.Version == xlMetaVersion && m.Format == xlMetaFormat + return isXLMetaValid(m.Version, m.Format) +} + +// Verifies if the backend format metadata is sane by validating +// the version string and format style. +func isXLMetaValid(version, format string) bool { + return ((version == xlMetaVersion || version == xlMetaVersion100) && + format == xlMetaFormat) +} + +// Converts metadata to object info. +func (m xlMetaV1) ToObjectInfo(bucket, object string) ObjectInfo { + objInfo := ObjectInfo{ + IsDir: false, + Bucket: bucket, + Name: object, + Size: m.Stat.Size, + ModTime: m.Stat.ModTime, + ContentType: m.Meta["content-type"], + ContentEncoding: m.Meta["content-encoding"], + } + + // Extract etag from metadata. + objInfo.ETag = extractETag(m.Meta) + + // etag/md5Sum has already been extracted. We need to + // remove to avoid it from appearing as part of + // response headers. e.g, X-Minio-* or X-Amz-*. + objInfo.UserDefined = cleanMetaETag(m.Meta) + + // Success. + return objInfo } // objectPartIndex - returns the index of matching object part number. diff --git a/cmd/xl-v1-multipart.go b/cmd/xl-v1-multipart.go index b2a2b6b52..7f266f1d1 100644 --- a/cmd/xl-v1-multipart.go +++ b/cmd/xl-v1-multipart.go @@ -973,7 +973,7 @@ func (xl xlObjects) CompleteMultipartUpload(bucket string, object string, upload xlMeta.Stat.ModTime = UTCNow() // Save successfully calculated md5sum. - xlMeta.Meta["md5Sum"] = s3MD5 + xlMeta.Meta["etag"] = s3MD5 uploadIDPath = path.Join(bucket, object, uploadID) tempUploadIDPath := uploadID @@ -1061,7 +1061,7 @@ func (xl xlObjects) CompleteMultipartUpload(bucket string, object string, upload Name: object, Size: xlMeta.Stat.Size, ModTime: xlMeta.Stat.ModTime, - MD5Sum: xlMeta.Meta["md5Sum"], + ETag: xlMeta.Meta["etag"], ContentType: xlMeta.Meta["content-type"], ContentEncoding: xlMeta.Meta["content-encoding"], UserDefined: xlMeta.Meta, diff --git a/cmd/xl-v1-object.go b/cmd/xl-v1-object.go index d7b041387..31bb2bfa6 100644 --- a/cmd/xl-v1-object.go +++ b/cmd/xl-v1-object.go @@ -101,23 +101,7 @@ func (xl xlObjects) CopyObject(srcBucket, srcObject, dstBucket, dstObject string if err = renameXLMetadata(onlineDisks, minioMetaTmpBucket, tempObj, srcBucket, srcObject, xl.writeQuorum); err != nil { return ObjectInfo{}, toObjectErr(err, srcBucket, srcObject) } - - objInfo := ObjectInfo{ - IsDir: false, - Bucket: srcBucket, - Name: srcObject, - Size: xlMeta.Stat.Size, - ModTime: xlMeta.Stat.ModTime, - MD5Sum: xlMeta.Meta["md5Sum"], - ContentType: xlMeta.Meta["content-type"], - ContentEncoding: xlMeta.Meta["content-encoding"], - } - // md5Sum has already been extracted into objInfo.MD5Sum. We - // need to remove it from xlMetaMap to avoid it from appearing as - // part of response headers. e.g, X-Minio-* or X-Amz-*. - delete(xlMeta.Meta, "md5Sum") - objInfo.UserDefined = xlMeta.Meta - return objInfo, nil + return xlMeta.ToObjectInfo(srcBucket, srcObject), nil } // Initialize pipe. @@ -333,10 +317,9 @@ func (xl xlObjects) GetObjectInfo(bucket, object string) (ObjectInfo, error) { // getObjectInfo - wrapper for reading object metadata and constructs ObjectInfo. func (xl xlObjects) getObjectInfo(bucket, object string) (objInfo ObjectInfo, err error) { - // returns xl meta map and stat info. + // Extracts xlStat and xlMetaMap. xlStat, xlMetaMap, err := xl.readXLMetaStat(bucket, object) if err != nil { - // Return error. return ObjectInfo{}, err } @@ -346,17 +329,19 @@ func (xl xlObjects) getObjectInfo(bucket, object string) (objInfo ObjectInfo, er Name: object, Size: xlStat.Size, ModTime: xlStat.ModTime, - MD5Sum: xlMetaMap["md5Sum"], ContentType: xlMetaMap["content-type"], ContentEncoding: xlMetaMap["content-encoding"], } - // md5Sum has already been extracted into objInfo.MD5Sum. We - // need to remove it from xlMetaMap to avoid it from appearing as - // part of response headers. e.g, X-Minio-* or X-Amz-*. + // Extract etag. + objInfo.ETag = extractETag(xlMetaMap) - delete(xlMetaMap, "md5Sum") - objInfo.UserDefined = xlMetaMap + // etag/md5Sum has already been extracted. We need to + // remove to avoid it from appearing as part of + // response headers. e.g, X-Minio-* or X-Amz-*. + objInfo.UserDefined = cleanMetaETag(xlMetaMap) + + // Success. return objInfo, nil } @@ -650,8 +635,8 @@ func (xl xlObjects) PutObject(bucket string, object string, size int64, data io. newMD5Hex := hex.EncodeToString(md5Writer.Sum(nil)) // Update the md5sum if not set with the newly calculated one. - if len(metadata["md5Sum"]) == 0 { - metadata["md5Sum"] = newMD5Hex + if len(metadata["etag"]) == 0 { + metadata["etag"] = newMD5Hex } // Guess content-type from the extension if possible. @@ -664,7 +649,7 @@ func (xl xlObjects) PutObject(bucket string, object string, size int64, data io. } // md5Hex representation. - md5Hex := metadata["md5Sum"] + md5Hex := metadata["etag"] if md5Hex != "" { if newMD5Hex != md5Hex { // Returns md5 mismatch. @@ -730,7 +715,7 @@ func (xl xlObjects) PutObject(bucket string, object string, size int64, data io. Name: object, Size: xlMeta.Stat.Size, ModTime: xlMeta.Stat.ModTime, - MD5Sum: xlMeta.Meta["md5Sum"], + ETag: xlMeta.Meta["etag"], ContentType: xlMeta.Meta["content-type"], ContentEncoding: xlMeta.Meta["content-encoding"], UserDefined: xlMeta.Meta, diff --git a/cmd/xl-v1-utils.go b/cmd/xl-v1-utils.go index 283b690ad..95f4cdb1d 100644 --- a/cmd/xl-v1-utils.go +++ b/cmd/xl-v1-utils.go @@ -253,6 +253,19 @@ func readXLMetaStat(disk StorageAPI, bucket string, object string) (statInfo, ma if err != nil { return statInfo{}, nil, traceError(err) } + + // obtain version. + xlVersion := parseXLVersion(xlMetaBuf) + + // obtain format. + xlFormat := parseXLFormat(xlMetaBuf) + + // Validate if the xl.json we read is sane, return corrupted format. + if !isXLMetaValid(xlVersion, xlFormat) { + // For version mismatchs and unrecognized format, return corrupted format. + return statInfo{}, nil, traceError(errCorruptedFormat) + } + // obtain xlMetaV1{}.Meta using `github.com/tidwall/gjson`. xlMetaMap := parseXLMetaMap(xlMetaBuf) @@ -261,6 +274,7 @@ func readXLMetaStat(disk StorageAPI, bucket string, object string) (statInfo, ma if err != nil { return statInfo{}, nil, traceError(err) } + // Return structured `xl.json`. return xlStat, xlMetaMap, nil } diff --git a/docs/backend/fs/fs.json b/docs/backend/fs/fs.json index 6b309dde3..7eda41dcb 100644 --- a/docs/backend/fs/fs.json +++ b/docs/backend/fs/fs.json @@ -5,6 +5,7 @@ "release": "DEVELOPMENT.GOGET" }, "meta": { + "etag": "97586a5290d4f5a41328062d6a7da593-3", "content-type": "binary/octet-stream", "content-encoding": "gzip" }, diff --git a/docs/backend/xl/xl.json b/docs/backend/xl/xl.json index 9cdc3ca06..6b37f0799 100644 --- a/docs/backend/xl/xl.json +++ b/docs/backend/xl/xl.json @@ -14,7 +14,7 @@ } ], "meta": { - "md5Sum": "97586a5290d4f5a41328062d6a7da593-3", + "etag": "97586a5290d4f5a41328062d6a7da593-3", "content-type": "application\/octet-stream", "content-encoding": "gzip" }, From 87fb911d3887820f3f89f209f01043d6cf083e9f Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Mon, 15 May 2017 00:52:33 -0700 Subject: [PATCH 24/80] Rename structs for azure and s3 gateway to be consistent. (#4347) --- cmd/gateway-azure-anonymous.go | 14 ++++---- cmd/gateway-azure-unsupported.go | 10 +++--- cmd/gateway-azure.go | 56 ++++++++++++++++---------------- cmd/gateway-s3-anonymous.go | 10 +++--- cmd/gateway-s3-unsupported.go | 10 +++--- cmd/gateway-s3.go | 54 +++++++++++++++--------------- 6 files changed, 77 insertions(+), 77 deletions(-) diff --git a/cmd/gateway-azure-anonymous.go b/cmd/gateway-azure-anonymous.go index 48b936343..c8f35e265 100644 --- a/cmd/gateway-azure-anonymous.go +++ b/cmd/gateway-azure-anonymous.go @@ -30,7 +30,7 @@ import ( ) // AnonGetBucketInfo - Get bucket metadata from azure anonymously. -func (a AzureObjects) AnonGetBucketInfo(bucket string) (bucketInfo BucketInfo, err error) { +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)) @@ -40,7 +40,7 @@ func (a AzureObjects) AnonGetBucketInfo(bucket string) (bucketInfo BucketInfo, e if err != nil { return bucketInfo, azureToObjectError(traceError(err), bucket) } - defer resp.Body.Close() + resp.Body.Close() if resp.StatusCode != http.StatusOK { return bucketInfo, azureToObjectError(traceError(anonErrToObjectErr(resp.StatusCode, bucket)), bucket) @@ -59,14 +59,14 @@ func (a AzureObjects) AnonGetBucketInfo(bucket string) (bucketInfo BucketInfo, e // AnonPutObject - SendPUT request without authentication. // This is needed when clients send PUT requests on objects that can be uploaded without auth. -func (a AzureObjects) AnonPutObject(bucket, object string, size int64, data io.Reader, metadata map[string]string, sha256sum string) (objInfo ObjectInfo, err error) { +func (a *azureObjects) AnonPutObject(bucket, object string, size int64, data io.Reader, metadata map[string]string, sha256sum string) (objInfo ObjectInfo, err error) { // azure doesn't support anonymous put return ObjectInfo{}, traceError(NotImplemented{}) } // 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) { +func (a *azureObjects) AnonGetObject(bucket, object string, startOffset int64, length int64, writer io.Writer) (err error) { u := a.client.GetBlobURL(bucket, object) req, err := http.NewRequest("GET", u, nil) if err != nil { @@ -95,12 +95,12 @@ func (a AzureObjects) AnonGetObject(bucket, object string, startOffset int64, le // AnonGetObjectInfo - Send HEAD request without authentication and convert the // result to ObjectInfo. -func (a AzureObjects) AnonGetObjectInfo(bucket, object string) (objInfo ObjectInfo, err error) { +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() + resp.Body.Close() if resp.StatusCode != http.StatusOK { return objInfo, azureToObjectError(traceError(anonErrToObjectErr(resp.StatusCode, bucket, object)), bucket, object) @@ -135,7 +135,7 @@ func (a AzureObjects) AnonGetObjectInfo(bucket, object string) (objInfo ObjectIn } // AnonListObjects - Use Azure equivalent ListBlobs. -func (a AzureObjects) AnonListObjects(bucket, prefix, marker, delimiter string, maxKeys int) (result ListObjectsInfo, err error) { +func (a *azureObjects) AnonListObjects(bucket, prefix, marker, delimiter string, maxKeys int) (result ListObjectsInfo, err error) { params := storage.ListBlobsParameters{ Prefix: prefix, Marker: marker, diff --git a/cmd/gateway-azure-unsupported.go b/cmd/gateway-azure-unsupported.go index c7468e367..c292275cb 100644 --- a/cmd/gateway-azure-unsupported.go +++ b/cmd/gateway-azure-unsupported.go @@ -17,27 +17,27 @@ package cmd // HealBucket - Not relevant. -func (a AzureObjects) HealBucket(bucket string) error { +func (a *azureObjects) HealBucket(bucket string) error { return traceError(NotImplemented{}) } // ListBucketsHeal - Not relevant. -func (a AzureObjects) ListBucketsHeal() (buckets []BucketInfo, err error) { +func (a *azureObjects) ListBucketsHeal() (buckets []BucketInfo, err error) { return nil, traceError(NotImplemented{}) } // HealObject - Not relevant. -func (a AzureObjects) HealObject(bucket, object string) (int, int, error) { +func (a *azureObjects) HealObject(bucket, object string) (int, int, error) { return 0, 0, traceError(NotImplemented{}) } // ListObjectsHeal - Not relevant. -func (a AzureObjects) ListObjectsHeal(bucket, prefix, marker, delimiter string, maxKeys int) (ListObjectsInfo, error) { +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, +func (a *azureObjects) ListUploadsHeal(bucket, prefix, marker, uploadIDMarker, delimiter string, maxUploads int) (ListMultipartsInfo, error) { return ListMultipartsInfo{}, traceError(NotImplemented{}) } diff --git a/cmd/gateway-azure.go b/cmd/gateway-azure.go index 22082b11c..2c5e87949 100644 --- a/cmd/gateway-azure.go +++ b/cmd/gateway-azure.go @@ -64,8 +64,8 @@ func (a *azureMultipartMetaInfo) del(key string) { delete(a.meta, key) } -// AzureObjects - Implements Object layer for Azure blob storage. -type AzureObjects struct { +// azureObjects - Implements Object layer for Azure blob storage. +type azureObjects struct { client storage.BlobStorageClient // Azure sdk client metaInfo azureMultipartMetaInfo } @@ -122,16 +122,16 @@ func azureToObjectError(err error, params ...string) error { return e } -// Inits azure blob storage client and returns AzureObjects. +// Inits azure blob storage client and returns azureObjects. func newAzureLayer(endPoint string, account, key string, secure bool) (GatewayLayer, error) { if endPoint == "" { endPoint = storage.DefaultBaseURL } c, err := storage.NewClient(account, key, endPoint, globalAzureAPIVersion, secure) if err != nil { - return AzureObjects{}, err + return &azureObjects{}, err } - return &AzureObjects{ + return &azureObjects{ client: c.GetBlobService(), metaInfo: azureMultipartMetaInfo{ meta: make(map[string]map[string]string), @@ -142,30 +142,30 @@ func newAzureLayer(endPoint string, account, key string, secure bool) (GatewayLa // Shutdown - save any gateway metadata to disk // if necessary and reload upon next restart. -func (a AzureObjects) Shutdown() error { +func (a *azureObjects) Shutdown() error { // TODO return nil } // StorageInfo - Not relevant to Azure backend. -func (a AzureObjects) StorageInfo() StorageInfo { +func (a *azureObjects) StorageInfo() StorageInfo { return StorageInfo{} } // MakeBucket - Create a new container on azure backend. -func (a AzureObjects) MakeBucket(bucket string) error { +func (a *azureObjects) MakeBucket(bucket string) error { // will never be called, only satisfy ObjectLayer interface return traceError(NotImplemented{}) } // MakeBucketWithLocation - Create a new container on azure backend. -func (a AzureObjects) MakeBucketWithLocation(bucket, location string) error { +func (a *azureObjects) MakeBucketWithLocation(bucket, location 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) { +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, @@ -188,7 +188,7 @@ func (a AzureObjects) GetBucketInfo(bucket string) (BucketInfo, error) { } // ListBuckets - Lists all azure containers, uses Azure equivalent ListContainers. -func (a AzureObjects) ListBuckets() (buckets []BucketInfo, err error) { +func (a *azureObjects) ListBuckets() (buckets []BucketInfo, err error) { resp, err := a.client.ListContainers(storage.ListContainersParameters{}) if err != nil { return nil, azureToObjectError(traceError(err)) @@ -207,13 +207,13 @@ func (a AzureObjects) ListBuckets() (buckets []BucketInfo, err error) { } // DeleteBucket - delete a container on azure, uses Azure equivalent DeleteContainer. -func (a AzureObjects) DeleteBucket(bucket string) error { +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) { +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, @@ -250,7 +250,7 @@ func (a AzureObjects) ListObjects(bucket, prefix, marker, delimiter string, maxK // // 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 { +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) @@ -273,7 +273,7 @@ func (a AzureObjects) GetObject(bucket, object string, startOffset int64, length // GetObjectInfo - reads blob metadata properties and replies back ObjectInfo, // uses zure equivalent GetBlobProperties. -func (a AzureObjects) GetObjectInfo(bucket, object string) (objInfo ObjectInfo, err error) { +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) @@ -311,7 +311,7 @@ func canonicalMetadata(metadata map[string]string) (canonical map[string]string) // 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) { +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 != "" { @@ -339,7 +339,7 @@ func (a AzureObjects) PutObject(bucket, object string, size int64, data io.Reade // 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) { +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) @@ -349,7 +349,7 @@ func (a AzureObjects) CopyObject(srcBucket, srcObject, destBucket, destObject st // DeleteObject - Deletes a blob on azure container, uses Azure // equivalent DeleteBlob API. -func (a AzureObjects) DeleteObject(bucket, object string) error { +func (a *azureObjects) DeleteObject(bucket, object string) error { err := a.client.DeleteBlob(bucket, object, nil) if err != nil { return azureToObjectError(traceError(err), bucket, object) @@ -361,7 +361,7 @@ func (a AzureObjects) DeleteObject(bucket, object string) error { // 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) { +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 @@ -377,7 +377,7 @@ func (a AzureObjects) ListMultipartUploads(bucket, prefix, keyMarker, uploadIDMa } // NewMultipartUpload - Use Azure equivalent CreateBlockBlob. -func (a AzureObjects) NewMultipartUpload(bucket, object string, metadata map[string]string) (uploadID string, err error) { +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. @@ -393,7 +393,7 @@ func (a AzureObjects) NewMultipartUpload(bucket, object string, metadata map[str } // CopyObjectPart - Not implemented. -func (a AzureObjects) CopyObjectPart(srcBucket, srcObject, destBucket, destObject string, uploadID string, partID int, startOffset int64, length int64) (info PartInfo, err error) { +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{}) } @@ -421,7 +421,7 @@ func azureParseBlockID(blockID string) (int, string, error) { } // 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) { +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{}) } @@ -453,7 +453,7 @@ func (a AzureObjects) PutObjectPart(bucket, object, uploadID string, partID int, } // ListObjectParts - Use Azure equivalent GetBlockList. -func (a AzureObjects) ListObjectParts(bucket, object, uploadID string, partNumberMarker int, maxParts int) (result ListPartsInfo, err error) { +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 @@ -502,13 +502,13 @@ func (a AzureObjects) ListObjectParts(bucket, object, uploadID string, partNumbe // 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 { +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) { +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}) @@ -598,7 +598,7 @@ func azureListBlobsGetParameters(p storage.ListBlobsParameters) url.Values { // 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, policyInfo policy.BucketAccessPolicy) error { +func (a *azureObjects) SetBucketPolicies(bucket string, policyInfo policy.BucketAccessPolicy) error { var policies []BucketAccessPolicy for prefix, policy := range policy.GetPolicies(policyInfo.Statements, bucket) { @@ -626,7 +626,7 @@ func (a AzureObjects) SetBucketPolicies(bucket string, policyInfo policy.BucketA } // GetBucketPolicies - Get the container ACL and convert it to canonical []bucketAccessPolicy -func (a AzureObjects) GetBucketPolicies(bucket string) (policy.BucketAccessPolicy, error) { +func (a *azureObjects) GetBucketPolicies(bucket string) (policy.BucketAccessPolicy, error) { policyInfo := policy.BucketAccessPolicy{Version: "2012-10-17"} perm, err := a.client.GetContainerPermissions(bucket, 0, "") if err != nil { @@ -644,7 +644,7 @@ func (a AzureObjects) GetBucketPolicies(bucket string) (policy.BucketAccessPolic } // DeleteBucketPolicies - Set the container ACL to "private" -func (a AzureObjects) DeleteBucketPolicies(bucket string) error { +func (a *azureObjects) DeleteBucketPolicies(bucket string) error { perm := storage.ContainerPermissions{ AccessType: storage.ContainerAccessTypePrivate, AccessPolicies: nil, diff --git a/cmd/gateway-s3-anonymous.go b/cmd/gateway-s3-anonymous.go index 4fbb1b716..147e8ea43 100644 --- a/cmd/gateway-s3-anonymous.go +++ b/cmd/gateway-s3-anonymous.go @@ -24,7 +24,7 @@ import ( ) // AnonPutObject creates a new object anonymously with the incoming data, -func (l *s3Gateway) AnonPutObject(bucket string, object string, size int64, data io.Reader, metadata map[string]string, sha256sum string) (ObjectInfo, error) { +func (l *s3Objects) AnonPutObject(bucket string, object string, size int64, data io.Reader, metadata map[string]string, sha256sum string) (ObjectInfo, error) { var sha256sumBytes []byte var err error @@ -54,7 +54,7 @@ func (l *s3Gateway) AnonPutObject(bucket string, object string, size int64, data } // AnonGetObject - Get object anonymously -func (l *s3Gateway) AnonGetObject(bucket string, key string, startOffset int64, length int64, writer io.Writer) error { +func (l *s3Objects) AnonGetObject(bucket string, key string, startOffset int64, length int64, writer io.Writer) error { r := minio.NewGetReqHeaders() if err := r.SetRange(startOffset, startOffset+length-1); err != nil { return s3ToObjectError(traceError(err), bucket, key) @@ -74,7 +74,7 @@ func (l *s3Gateway) AnonGetObject(bucket string, key string, startOffset int64, } // AnonGetObjectInfo - Get object info anonymously -func (l *s3Gateway) AnonGetObjectInfo(bucket string, object string) (ObjectInfo, error) { +func (l *s3Objects) AnonGetObjectInfo(bucket string, object string) (ObjectInfo, error) { r := minio.NewHeadReqHeaders() oi, err := l.anonClient.StatObject(bucket, object, r) if err != nil { @@ -85,7 +85,7 @@ func (l *s3Gateway) AnonGetObjectInfo(bucket string, object string) (ObjectInfo, } // AnonListObjects - List objects anonymously -func (l *s3Gateway) AnonListObjects(bucket string, prefix string, marker string, delimiter string, maxKeys int) (ListObjectsInfo, error) { +func (l *s3Objects) AnonListObjects(bucket string, prefix string, marker string, delimiter string, maxKeys int) (ListObjectsInfo, error) { result, err := l.anonClient.ListObjects(bucket, prefix, marker, delimiter, maxKeys) if err != nil { return ListObjectsInfo{}, s3ToObjectError(traceError(err), bucket) @@ -95,7 +95,7 @@ func (l *s3Gateway) AnonListObjects(bucket string, prefix string, marker string, } // AnonGetBucketInfo - Get bucket metadata anonymously. -func (l *s3Gateway) AnonGetBucketInfo(bucket string) (BucketInfo, error) { +func (l *s3Objects) AnonGetBucketInfo(bucket string) (BucketInfo, error) { if exists, err := l.anonClient.BucketExists(bucket); err != nil { return BucketInfo{}, s3ToObjectError(traceError(err), bucket) } else if !exists { diff --git a/cmd/gateway-s3-unsupported.go b/cmd/gateway-s3-unsupported.go index fbcabed76..13d167b8a 100644 --- a/cmd/gateway-s3-unsupported.go +++ b/cmd/gateway-s3-unsupported.go @@ -17,26 +17,26 @@ package cmd // HealBucket - Not relevant. -func (l *s3Gateway) HealBucket(bucket string) error { +func (l *s3Objects) HealBucket(bucket string) error { return traceError(NotImplemented{}) } // ListBucketsHeal - Not relevant. -func (l *s3Gateway) ListBucketsHeal() (buckets []BucketInfo, err error) { +func (l *s3Objects) ListBucketsHeal() (buckets []BucketInfo, err error) { return []BucketInfo{}, traceError(NotImplemented{}) } // HealObject - Not relevant. -func (l *s3Gateway) HealObject(bucket string, object string) (int, int, error) { +func (l *s3Objects) HealObject(bucket string, object string) (int, int, error) { return 0, 0, traceError(NotImplemented{}) } // ListObjectsHeal - Not relevant. -func (l *s3Gateway) ListObjectsHeal(bucket string, prefix string, marker string, delimiter string, maxKeys int) (ListObjectsInfo, error) { +func (l *s3Objects) ListObjectsHeal(bucket string, prefix string, marker string, delimiter string, maxKeys int) (ListObjectsInfo, error) { return ListObjectsInfo{}, traceError(NotImplemented{}) } // ListUploadsHeal - Not relevant. -func (l *s3Gateway) ListUploadsHeal(bucket string, prefix string, marker string, uploadIDMarker string, delimiter string, maxUploads int) (ListMultipartsInfo, error) { +func (l *s3Objects) ListUploadsHeal(bucket string, prefix string, marker string, uploadIDMarker string, delimiter string, maxUploads int) (ListMultipartsInfo, error) { return ListMultipartsInfo{}, traceError(NotImplemented{}) } diff --git a/cmd/gateway-s3.go b/cmd/gateway-s3.go index 2a09d5fc7..f2c2e49b5 100644 --- a/cmd/gateway-s3.go +++ b/cmd/gateway-s3.go @@ -91,8 +91,8 @@ func s3ToObjectError(err error, params ...string) error { return e } -// s3Gateway implements gateway for Minio and S3 compatible object storage servers. -type s3Gateway struct { +// s3Objects implements gateway for Minio and S3 compatible object storage servers. +type s3Objects struct { Client *minio.Core anonClient *minio.Core } @@ -115,7 +115,7 @@ func newS3Gateway(endpoint string, accessKey, secretKey string, secure bool) (Ga return nil, err } - return &s3Gateway{ + return &s3Objects{ Client: client, anonClient: anonClient, }, nil @@ -123,24 +123,24 @@ func newS3Gateway(endpoint string, accessKey, secretKey string, secure bool) (Ga // Shutdown saves any gateway metadata to disk // if necessary and reload upon next restart. -func (l *s3Gateway) Shutdown() error { +func (l *s3Objects) Shutdown() error { // TODO return nil } // StorageInfo is not relevant to S3 backend. -func (l *s3Gateway) StorageInfo() StorageInfo { +func (l *s3Objects) StorageInfo() StorageInfo { return StorageInfo{} } // MakeBucket creates a new container on S3 backend. -func (l *s3Gateway) MakeBucket(bucket string) error { +func (l *s3Objects) MakeBucket(bucket string) error { // will never be called, only satisfy ObjectLayer interface return traceError(NotImplemented{}) } // MakeBucket creates a new container on S3 backend. -func (l *s3Gateway) MakeBucketWithLocation(bucket, location string) error { +func (l *s3Objects) MakeBucketWithLocation(bucket, location string) error { err := l.Client.MakeBucket(bucket, location) if err != nil { return s3ToObjectError(traceError(err), bucket) @@ -149,7 +149,7 @@ func (l *s3Gateway) MakeBucketWithLocation(bucket, location string) error { } // GetBucketInfo gets bucket metadata.. -func (l *s3Gateway) GetBucketInfo(bucket string) (BucketInfo, error) { +func (l *s3Objects) GetBucketInfo(bucket string) (BucketInfo, error) { buckets, err := l.Client.ListBuckets() if err != nil { return BucketInfo{}, s3ToObjectError(traceError(err), bucket) @@ -170,7 +170,7 @@ func (l *s3Gateway) GetBucketInfo(bucket string) (BucketInfo, error) { } // ListBuckets lists all S3 buckets -func (l *s3Gateway) ListBuckets() ([]BucketInfo, error) { +func (l *s3Objects) ListBuckets() ([]BucketInfo, error) { buckets, err := l.Client.ListBuckets() if err != nil { return nil, err @@ -188,7 +188,7 @@ func (l *s3Gateway) ListBuckets() ([]BucketInfo, error) { } // DeleteBucket deletes a bucket on S3 -func (l *s3Gateway) DeleteBucket(bucket string) error { +func (l *s3Objects) DeleteBucket(bucket string) error { err := l.Client.RemoveBucket(bucket) if err != nil { return s3ToObjectError(traceError(err), bucket) @@ -197,7 +197,7 @@ func (l *s3Gateway) DeleteBucket(bucket string) error { } // ListObjects lists all blobs in S3 bucket filtered by prefix -func (l *s3Gateway) ListObjects(bucket string, prefix string, marker string, delimiter string, maxKeys int) (ListObjectsInfo, error) { +func (l *s3Objects) ListObjects(bucket string, prefix string, marker string, delimiter string, maxKeys int) (ListObjectsInfo, error) { result, err := l.Client.ListObjects(bucket, prefix, marker, delimiter, maxKeys) if err != nil { return ListObjectsInfo{}, s3ToObjectError(traceError(err), bucket) @@ -207,7 +207,7 @@ func (l *s3Gateway) ListObjects(bucket string, prefix string, marker string, del } // ListObjectsV2 lists all blobs in S3 bucket filtered by prefix -func (l *s3Gateway) ListObjectsV2(bucket, prefix, continuationToken string, fetchOwner bool, delimiter string, maxKeys int) (ListObjectsV2Info, error) { +func (l *s3Objects) ListObjectsV2(bucket, prefix, continuationToken string, fetchOwner bool, delimiter string, maxKeys int) (ListObjectsV2Info, error) { result, err := l.Client.ListObjectsV2(bucket, prefix, continuationToken, fetchOwner, delimiter, maxKeys) if err != nil { return ListObjectsV2Info{}, s3ToObjectError(traceError(err), bucket) @@ -266,7 +266,7 @@ func fromMinioClientListBucketResult(bucket string, result minio.ListBucketResul // // startOffset indicates the starting read location of the object. // length indicates the total length of the object. -func (l *s3Gateway) GetObject(bucket string, key string, startOffset int64, length int64, writer io.Writer) error { +func (l *s3Objects) GetObject(bucket string, key string, startOffset int64, length int64, writer io.Writer) error { r := minio.NewGetReqHeaders() if err := r.SetRange(startOffset, startOffset+length-1); err != nil { return s3ToObjectError(traceError(err), bucket, key) @@ -303,7 +303,7 @@ func fromMinioClientObjectInfo(bucket string, oi minio.ObjectInfo) ObjectInfo { } // GetObjectInfo reads object info and replies back ObjectInfo -func (l *s3Gateway) GetObjectInfo(bucket string, object string) (objInfo ObjectInfo, err error) { +func (l *s3Objects) GetObjectInfo(bucket string, object string) (objInfo ObjectInfo, err error) { r := minio.NewHeadReqHeaders() oi, err := l.Client.StatObject(bucket, object, r) if err != nil { @@ -314,7 +314,7 @@ func (l *s3Gateway) GetObjectInfo(bucket string, object string) (objInfo ObjectI } // PutObject creates a new object with the incoming data, -func (l *s3Gateway) PutObject(bucket string, object string, size int64, data io.Reader, metadata map[string]string, sha256sum string) (ObjectInfo, error) { +func (l *s3Objects) PutObject(bucket string, object string, size int64, data io.Reader, metadata map[string]string, sha256sum string) (ObjectInfo, error) { var sha256sumBytes []byte var err error @@ -344,7 +344,7 @@ func (l *s3Gateway) PutObject(bucket string, object string, size int64, data io. } // CopyObject copies a blob from source container to destination container. -func (l *s3Gateway) CopyObject(srcBucket string, srcObject string, destBucket string, destObject string, metadata map[string]string) (ObjectInfo, error) { +func (l *s3Objects) CopyObject(srcBucket string, srcObject string, destBucket string, destObject string, metadata map[string]string) (ObjectInfo, error) { err := l.Client.CopyObject(destBucket, destObject, path.Join(srcBucket, srcObject), minio.CopyConditions{}) if err != nil { return ObjectInfo{}, s3ToObjectError(traceError(err), srcBucket, srcObject) @@ -359,7 +359,7 @@ func (l *s3Gateway) CopyObject(srcBucket string, srcObject string, destBucket st } // DeleteObject deletes a blob in bucket -func (l *s3Gateway) DeleteObject(bucket string, object string) error { +func (l *s3Objects) DeleteObject(bucket string, object string) error { err := l.Client.RemoveObject(bucket, object) if err != nil { return s3ToObjectError(traceError(err), bucket, object) @@ -407,7 +407,7 @@ func fromMinioClientListMultipartsInfo(lmur minio.ListMultipartUploadsResult) Li } // ListMultipartUploads lists all multipart uploads. -func (l *s3Gateway) ListMultipartUploads(bucket string, prefix string, keyMarker string, uploadIDMarker string, delimiter string, maxUploads int) (ListMultipartsInfo, error) { +func (l *s3Objects) ListMultipartUploads(bucket string, prefix string, keyMarker string, uploadIDMarker string, delimiter string, maxUploads int) (ListMultipartsInfo, error) { result, err := l.Client.ListMultipartUploads(bucket, prefix, keyMarker, uploadIDMarker, delimiter, maxUploads) if err != nil { return ListMultipartsInfo{}, err @@ -435,12 +435,12 @@ func toMinioClientMetadata(metadata map[string]string) map[string][]string { } // NewMultipartUpload upload object in multiple parts -func (l *s3Gateway) NewMultipartUpload(bucket string, object string, metadata map[string]string) (uploadID string, err error) { +func (l *s3Objects) NewMultipartUpload(bucket string, object string, metadata map[string]string) (uploadID string, err error) { return l.Client.NewMultipartUpload(bucket, object, toMinioClientMetadata(metadata)) } // CopyObjectPart copy part of object to other bucket and object -func (l *s3Gateway) CopyObjectPart(srcBucket string, srcObject string, destBucket string, destObject string, uploadID string, partID int, startOffset int64, length int64) (info PartInfo, err error) { +func (l *s3Objects) CopyObjectPart(srcBucket string, srcObject string, destBucket string, destObject string, uploadID string, partID int, startOffset int64, length int64) (info PartInfo, err error) { // FIXME: implement CopyObjectPart return PartInfo{}, traceError(NotImplemented{}) } @@ -456,7 +456,7 @@ func fromMinioClientObjectPart(op minio.ObjectPart) PartInfo { } // PutObjectPart puts a part of object in bucket -func (l *s3Gateway) PutObjectPart(bucket string, object string, uploadID string, partID int, size int64, data io.Reader, md5Hex string, sha256sum string) (PartInfo, error) { +func (l *s3Objects) PutObjectPart(bucket string, object string, uploadID string, partID int, size int64, data io.Reader, md5Hex string, sha256sum string) (PartInfo, error) { md5HexBytes, err := hex.DecodeString(md5Hex) if err != nil { return PartInfo{}, err @@ -501,7 +501,7 @@ func fromMinioClientListPartsInfo(lopr minio.ListObjectPartsResult) ListPartsInf } // ListObjectParts returns all object parts for specified object in specified bucket -func (l *s3Gateway) ListObjectParts(bucket string, object string, uploadID string, partNumberMarker int, maxParts int) (ListPartsInfo, error) { +func (l *s3Objects) ListObjectParts(bucket string, object string, uploadID string, partNumberMarker int, maxParts int) (ListPartsInfo, error) { result, err := l.Client.ListObjectParts(bucket, object, uploadID, partNumberMarker, maxParts) if err != nil { return ListPartsInfo{}, err @@ -511,7 +511,7 @@ func (l *s3Gateway) ListObjectParts(bucket string, object string, uploadID strin } // AbortMultipartUpload aborts a ongoing multipart upload -func (l *s3Gateway) AbortMultipartUpload(bucket string, object string, uploadID string) error { +func (l *s3Objects) AbortMultipartUpload(bucket string, object string, uploadID string) error { return l.Client.AbortMultipartUpload(bucket, object, uploadID) } @@ -533,7 +533,7 @@ func toMinioClientCompleteParts(parts []completePart) []minio.CompletePart { } // CompleteMultipartUpload completes ongoing multipart upload and finalizes object -func (l *s3Gateway) CompleteMultipartUpload(bucket string, object string, uploadID string, uploadedParts []completePart) (ObjectInfo, error) { +func (l *s3Objects) CompleteMultipartUpload(bucket string, object string, uploadID string, uploadedParts []completePart) (ObjectInfo, error) { err := l.Client.CompleteMultipartUpload(bucket, object, uploadID, toMinioClientCompleteParts(uploadedParts)) if err != nil { return ObjectInfo{}, s3ToObjectError(traceError(err), bucket, object) @@ -543,7 +543,7 @@ func (l *s3Gateway) CompleteMultipartUpload(bucket string, object string, upload } // SetBucketPolicies sets policy on bucket -func (l *s3Gateway) SetBucketPolicies(bucket string, policyInfo policy.BucketAccessPolicy) error { +func (l *s3Objects) SetBucketPolicies(bucket string, policyInfo policy.BucketAccessPolicy) error { if err := l.Client.PutBucketPolicy(bucket, policyInfo); err != nil { return s3ToObjectError(traceError(err), bucket, "") } @@ -552,7 +552,7 @@ func (l *s3Gateway) SetBucketPolicies(bucket string, policyInfo policy.BucketAcc } // GetBucketPolicies will get policy on bucket -func (l *s3Gateway) GetBucketPolicies(bucket string) (policy.BucketAccessPolicy, error) { +func (l *s3Objects) GetBucketPolicies(bucket string) (policy.BucketAccessPolicy, error) { policyInfo, err := l.Client.GetBucketPolicy(bucket) if err != nil { return policy.BucketAccessPolicy{}, s3ToObjectError(traceError(err), bucket, "") @@ -561,7 +561,7 @@ func (l *s3Gateway) GetBucketPolicies(bucket string) (policy.BucketAccessPolicy, } // DeleteBucketPolicies deletes all policies on bucket -func (l *s3Gateway) DeleteBucketPolicies(bucket string) error { +func (l *s3Objects) DeleteBucketPolicies(bucket string) error { if err := l.Client.PutBucketPolicy(bucket, policy.BucketAccessPolicy{}); err != nil { return s3ToObjectError(traceError(err), bucket, "") } From 465274cd2175a05c8ce22e27f9c08315129aae0a Mon Sep 17 00:00:00 2001 From: Anis Elleuch Date: Mon, 15 May 2017 16:28:47 +0200 Subject: [PATCH 25/80] server-info: Change Error type to string (#4346) Golang std error type doesn't marshal/unmarshal with json. So errors are not actually being sent when a client calls ServerInfo() API. --- cmd/admin-handlers.go | 8 ++++---- cmd/admin-handlers_test.go | 2 +- pkg/madmin/info-commands.go | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cmd/admin-handlers.go b/cmd/admin-handlers.go index 8396e4e6b..3d4f34852 100644 --- a/cmd/admin-handlers.go +++ b/cmd/admin-handlers.go @@ -241,9 +241,9 @@ type ServerInfoData struct { // ServerInfo holds server information result of one node type ServerInfo struct { - Error error - Addr string - Data *ServerInfoData + Error string `json:"error"` + Addr string `json:"addr"` + Data *ServerInfoData `json:"data"` } // ServerInfoHandler - GET /?info @@ -276,7 +276,7 @@ func (adminAPI adminAPIHandlers) ServerInfoHandler(w http.ResponseWriter, r *htt serverInfoData, err := peer.cmdRunner.ServerInfoData() if err != nil { errorIf(err, "Unable to get server info from %s.", peer.addr) - reply[idx].Error = err + reply[idx].Error = err.Error() return } diff --git a/cmd/admin-handlers_test.go b/cmd/admin-handlers_test.go index a8d984f9e..68ccdd80e 100644 --- a/cmd/admin-handlers_test.go +++ b/cmd/admin-handlers_test.go @@ -1314,7 +1314,7 @@ func TestAdminServerInfo(t *testing.T) { if len(serverInfo.Addr) == 0 { t.Error("Expected server address to be non empty") } - if serverInfo.Error != nil { + if serverInfo.Error != "" { t.Errorf("Unexpected error = %v\n", serverInfo.Error) } if serverInfo.Data.StorageInfo.Free == 0 { diff --git a/pkg/madmin/info-commands.go b/pkg/madmin/info-commands.go index 01dcff234..f6f93265e 100644 --- a/pkg/madmin/info-commands.go +++ b/pkg/madmin/info-commands.go @@ -84,7 +84,7 @@ type ServerInfoData struct { // ServerInfo holds server information result of one node type ServerInfo struct { - Error error `json:"error"` + Error string `json:"error"` Addr string `json:"addr"` Data *ServerInfoData `json:"data"` } From 5db1e9f3dd74ecce06cbab1cf1c52930a62a302f Mon Sep 17 00:00:00 2001 From: Krishna Srinivas Date: Mon, 15 May 2017 18:17:02 -0700 Subject: [PATCH 26/80] signature: use region from Auth header if server's region not configured (#4329) --- cmd/bucket-notification-utils.go | 59 +++++++++++++--------- cmd/bucket-notification-utils_test.go | 70 +++++++++++++++++++++++---- cmd/config-v18.go | 5 -- cmd/globals.go | 2 +- cmd/handler-utils.go | 4 +- cmd/post-policy_test.go | 26 +++++----- cmd/server-startup-msg.go | 8 +-- cmd/signature-v4-parser.go | 3 -- cmd/signature-v4-parser_test.go | 19 ++------ cmd/signature-v4-utils.go | 5 +- cmd/signature-v4-utils_test.go | 2 +- cmd/signature-v4.go | 2 +- cmd/signature-v4_test.go | 41 ++++------------ 13 files changed, 138 insertions(+), 108 deletions(-) diff --git a/cmd/bucket-notification-utils.go b/cmd/bucket-notification-utils.go index da9836794..c643c69d2 100644 --- a/cmd/bucket-notification-utils.go +++ b/cmd/bucket-notification-utils.go @@ -17,6 +17,7 @@ package cmd import ( + "errors" "strings" "github.com/minio/minio-go/pkg/set" @@ -111,20 +112,21 @@ func checkARN(arn, arnType string) APIErrorCode { if !strings.HasPrefix(arn, arnType) { return ErrARNNotification } - if !strings.HasPrefix(arn, arnType+serverConfig.GetRegion()+":") { - return ErrRegionNotification - } - account := strings.SplitN(strings.TrimPrefix(arn, arnType+serverConfig.GetRegion()+":"), ":", 2) - switch len(account) { - case 1: - // This means ARN is malformed, account should have min of 2elements. + strs := strings.SplitN(arn, ":", -1) + if len(strs) != 6 { return ErrARNNotification - case 2: - // Account topic id or topic name cannot be empty. - if account[0] == "" || account[1] == "" { - return ErrARNNotification + } + if serverConfig.GetRegion() != "" { + region := strs[3] + if region != serverConfig.GetRegion() { + return ErrRegionNotification } } + accountID := strs[4] + resource := strs[5] + if accountID == "" || resource == "" { + return ErrARNNotification + } return ErrNone } @@ -258,28 +260,39 @@ func validateNotificationConfig(nConfig notificationConfig) APIErrorCode { // - webhook func unmarshalSqsARN(queueARN string) (mSqs arnSQS) { mSqs = arnSQS{} - if !strings.HasPrefix(queueARN, minioSqs+serverConfig.GetRegion()+":") { + strs := strings.SplitN(queueARN, ":", -1) + if len(strs) != 6 { return mSqs } - sqsType := strings.TrimPrefix(queueARN, minioSqs+serverConfig.GetRegion()+":") - switch { - case hasSuffix(sqsType, queueTypeAMQP): + if serverConfig.GetRegion() != "" { + region := strs[3] + if region != serverConfig.GetRegion() { + return mSqs + } + } + sqsType := strs[5] + switch sqsType { + case queueTypeAMQP: mSqs.Type = queueTypeAMQP - case hasSuffix(sqsType, queueTypeNATS): + case queueTypeNATS: mSqs.Type = queueTypeNATS - case hasSuffix(sqsType, queueTypeElastic): + case queueTypeElastic: mSqs.Type = queueTypeElastic - case hasSuffix(sqsType, queueTypeRedis): + case queueTypeRedis: mSqs.Type = queueTypeRedis - case hasSuffix(sqsType, queueTypePostgreSQL): + case queueTypePostgreSQL: mSqs.Type = queueTypePostgreSQL - case hasSuffix(sqsType, queueTypeMySQL): + case queueTypeMySQL: mSqs.Type = queueTypeMySQL - case hasSuffix(sqsType, queueTypeKafka): + case queueTypeKafka: mSqs.Type = queueTypeKafka - case hasSuffix(sqsType, queueTypeWebhook): + case queueTypeWebhook: mSqs.Type = queueTypeWebhook + default: + errorIf(errors.New("invalid SQS type"), "SQS type: %s", sqsType) } // Add more queues here. - mSqs.AccountID = strings.TrimSuffix(sqsType, ":"+mSqs.Type) + + mSqs.AccountID = strs[4] + return mSqs } diff --git a/cmd/bucket-notification-utils_test.go b/cmd/bucket-notification-utils_test.go index 22d64be3a..e98907b21 100644 --- a/cmd/bucket-notification-utils_test.go +++ b/cmd/bucket-notification-utils_test.go @@ -259,11 +259,6 @@ func TestQueueARN(t *testing.T) { queueARN: "arn:minio:sns:us-east-1:1:listen", errCode: ErrARNNotification, }, - // Invalid region 'us-west-1' in queue arn. - { - queueARN: "arn:minio:sqs:us-west-1:1:redis", - errCode: ErrRegionNotification, - }, // Invalid queue name empty in queue arn. { queueARN: "arn:minio:sqs:us-east-1:1:", @@ -298,6 +293,37 @@ func TestQueueARN(t *testing.T) { t.Errorf("Test %d: Expected \"%d\", got \"%d\"", i+1, testCase.errCode, errCode) } } + + // Test when server region is set. + rootPath, err = newTestConfig("us-east-1") + if err != nil { + t.Fatalf("unable initialize config file, %s", err) + } + defer removeAll(rootPath) + + testCases = []struct { + queueARN string + errCode APIErrorCode + }{ + // Incorrect region should produce error. + { + queueARN: "arn:minio:sqs:us-west-1:1:webhook", + errCode: ErrRegionNotification, + }, + // Correct region should not produce error. + { + queueARN: "arn:minio:sqs:us-east-1:1:webhook", + errCode: ErrNone, + }, + } + + // Validate all tests for queue arn. + for i, testCase := range testCases { + errCode := checkQueueARN(testCase.queueARN) + if testCase.errCode != errCode { + t.Errorf("Test %d: Expected \"%d\", got \"%d\"", i+1, testCase.errCode, errCode) + } + } } // Test unmarshal queue arn. @@ -337,11 +363,6 @@ func TestUnmarshalSQSARN(t *testing.T) { queueARN: "", Type: "", }, - // Invalid region 'us-west-1' in queue arn. - { - queueARN: "arn:minio:sqs:us-west-1:1:redis", - Type: "", - }, // Partial queue arn. { queueARN: "arn:minio:sqs:", @@ -361,4 +382,33 @@ func TestUnmarshalSQSARN(t *testing.T) { } } + // Test when the server region is set. + rootPath, err = newTestConfig("us-east-1") + if err != nil { + t.Fatalf("unable initialize config file, %s", err) + } + defer removeAll(rootPath) + + testCases = []struct { + queueARN string + Type string + }{ + // Incorrect region in ARN returns empty mSqs.Type + { + queueARN: "arn:minio:sqs:us-west-1:1:webhook", + Type: "", + }, + // Correct regionin ARN returns valid mSqs.Type + { + queueARN: "arn:minio:sqs:us-east-1:1:webhook", + Type: "webhook", + }, + } + + for i, testCase := range testCases { + mSqs := unmarshalSqsARN(testCase.queueARN) + if testCase.Type != mSqs.Type { + t.Errorf("Test %d: Expected \"%s\", got \"%s\"", i+1, testCase.Type, mSqs.Type) + } + } } diff --git a/cmd/config-v18.go b/cmd/config-v18.go index f75c0cb4d..048f02451 100644 --- a/cmd/config-v18.go +++ b/cmd/config-v18.go @@ -261,11 +261,6 @@ func getValidConfig() (*serverConfigV18, error) { return nil, err } - // Validate region field - if srvCfg.Region == "" { - return nil, errors.New("Region config value cannot be empty") - } - // Validate credential fields only when // they are not set via the environment diff --git a/cmd/globals.go b/cmd/globals.go index 13e4f1638..a9444dd88 100644 --- a/cmd/globals.go +++ b/cmd/globals.go @@ -29,7 +29,7 @@ import ( const ( globalMinioCertExpireWarnDays = time.Hour * 24 * 30 // 30 days. - globalMinioDefaultRegion = "us-east-1" + globalMinioDefaultRegion = "" globalMinioDefaultOwnerID = "minio" globalMinioDefaultStorageClass = "STANDARD" globalWindowsOSName = "windows" diff --git a/cmd/handler-utils.go b/cmd/handler-utils.go index 46b1d4158..7a1d80f13 100644 --- a/cmd/handler-utils.go +++ b/cmd/handler-utils.go @@ -38,7 +38,7 @@ func parseLocationConstraint(r *http.Request) (location string, s3Error APIError } // else for both err as nil or io.EOF location = locationConstraint.Location if location == "" { - location = globalMinioDefaultRegion + location = serverConfig.GetRegion() } return location, ErrNone } @@ -46,7 +46,7 @@ func parseLocationConstraint(r *http.Request) (location string, s3Error APIError // Validates input location is same as configured region // of Minio server. func isValidLocation(location string) bool { - return serverConfig.GetRegion() == location + return serverConfig.GetRegion() == "" || serverConfig.GetRegion() == location } // Supported headers that needs to be extracted. diff --git a/cmd/post-policy_test.go b/cmd/post-policy_test.go index 97a7e483e..2f03ae2fa 100644 --- a/cmd/post-policy_test.go +++ b/cmd/post-policy_test.go @@ -246,6 +246,7 @@ func testPostPolicyBucketHandler(obj ObjectLayer, instanceType string, t TestErr } } + region := "us-east-1" // Test cases for signature-V4. testCasesV4BadData := []struct { objectName string @@ -330,7 +331,7 @@ func testPostPolicyBucketHandler(obj ObjectLayer, instanceType string, t TestErr testCase.policy = fmt.Sprintf(testCase.policy, testCase.dates...) req, perr := newPostRequestV4Generic("", bucketName, testCase.objectName, testCase.data, testCase.accessKey, - testCase.secretKey, curTime, []byte(testCase.policy), nil, testCase.corruptedBase64, testCase.corruptedMultipart) + testCase.secretKey, region, curTime, []byte(testCase.policy), nil, testCase.corruptedBase64, testCase.corruptedMultipart) if perr != nil { t.Fatalf("Test %d: %s: Failed to create HTTP request for PostPolicyHandler: %v", i+1, instanceType, perr) } @@ -473,9 +474,10 @@ func testPostPolicyBucketHandlerRedirect(obj ObjectLayer, instanceType string, t // Generate the final policy document policy = fmt.Sprintf(policy, dates...) + region := "us-east-1" // Create a new POST request with success_action_redirect field specified req, perr := newPostRequestV4Generic("", bucketName, keyName, []byte("objData"), - credentials.AccessKey, credentials.SecretKey, curTime, + credentials.AccessKey, credentials.SecretKey, region, curTime, []byte(policy), map[string]string{"success_action_redirect": redirectURL.String()}, false, false) if perr != nil { @@ -565,11 +567,11 @@ func newPostRequestV2(endPoint, bucketName, objectName string, accessKey, secret return req, nil } -func buildGenericPolicy(t time.Time, accessKey, bucketName, objectName string, contentLengthRange bool) []byte { +func buildGenericPolicy(t time.Time, accessKey, region, bucketName, objectName string, contentLengthRange bool) []byte { // Expire the request five minutes from now. expirationTime := t.Add(time.Minute * 5) - credStr := getCredentialString(accessKey, serverConfig.GetRegion(), t) + credStr := getCredentialString(accessKey, region, t) // Create a new post policy. policy := newPostPolicyBytesV4(credStr, bucketName, objectName, expirationTime) if contentLengthRange { @@ -578,10 +580,10 @@ func buildGenericPolicy(t time.Time, accessKey, bucketName, objectName string, c return policy } -func newPostRequestV4Generic(endPoint, bucketName, objectName string, objData []byte, accessKey, secretKey string, +func newPostRequestV4Generic(endPoint, bucketName, objectName string, objData []byte, accessKey, secretKey string, region string, t time.Time, policy []byte, addFormData map[string]string, corruptedB64 bool, corruptedMultipart bool) (*http.Request, error) { // Get the user credential. - credStr := getCredentialString(accessKey, serverConfig.GetRegion(), t) + credStr := getCredentialString(accessKey, region, t) // Only need the encoding. encodedPolicy := base64.StdEncoding.EncodeToString(policy) @@ -591,7 +593,7 @@ func newPostRequestV4Generic(endPoint, bucketName, objectName string, objData [] } // Presign with V4 signature based on the policy. - signature := postPresignSignatureV4(encodedPolicy, t, secretKey, serverConfig.GetRegion()) + signature := postPresignSignatureV4(encodedPolicy, t, secretKey, region) formData := map[string]string{ "bucket": bucketName, @@ -645,12 +647,14 @@ func newPostRequestV4Generic(endPoint, bucketName, objectName string, objData [] func newPostRequestV4WithContentLength(endPoint, bucketName, objectName string, objData []byte, accessKey, secretKey string) (*http.Request, error) { t := UTCNow() - policy := buildGenericPolicy(t, accessKey, bucketName, objectName, true) - return newPostRequestV4Generic(endPoint, bucketName, objectName, objData, accessKey, secretKey, t, policy, nil, false, false) + region := "us-east-1" + policy := buildGenericPolicy(t, accessKey, region, bucketName, objectName, true) + return newPostRequestV4Generic(endPoint, bucketName, objectName, objData, accessKey, secretKey, region, t, policy, nil, false, false) } func newPostRequestV4(endPoint, bucketName, objectName string, objData []byte, accessKey, secretKey string) (*http.Request, error) { t := UTCNow() - policy := buildGenericPolicy(t, accessKey, bucketName, objectName, false) - return newPostRequestV4Generic(endPoint, bucketName, objectName, objData, accessKey, secretKey, t, policy, nil, false, false) + region := "us-east-1" + policy := buildGenericPolicy(t, accessKey, region, bucketName, objectName, false) + return newPostRequestV4Generic(endPoint, bucketName, objectName, objData, accessKey, secretKey, region, t, policy, nil, false, false) } diff --git a/cmd/server-startup-msg.go b/cmd/server-startup-msg.go index a9298ce65..bd23bd63e 100644 --- a/cmd/server-startup-msg.go +++ b/cmd/server-startup-msg.go @@ -79,7 +79,9 @@ func printServerCommonMsg(apiEndpoints []string) { log.Println(colorBlue("\nEndpoint: ") + colorBold(fmt.Sprintf(getFormatStr(len(apiEndpointStr), 1), apiEndpointStr))) log.Println(colorBlue("AccessKey: ") + colorBold(fmt.Sprintf("%s ", cred.AccessKey))) log.Println(colorBlue("SecretKey: ") + colorBold(fmt.Sprintf("%s ", cred.SecretKey))) - log.Println(colorBlue("Region: ") + colorBold(fmt.Sprintf(getFormatStr(len(region), 3), region))) + if region != "" { + log.Println(colorBlue("Region: ") + colorBold(fmt.Sprintf(getFormatStr(len(region), 3), region))) + } printEventNotifiers() log.Println(colorBlue("\nBrowser Access:")) @@ -92,12 +94,12 @@ func printEventNotifiers() { // In case initEventNotifier() was not done or failed. return } - arnMsg := colorBlue("SQS ARNs: ") // Get all configured external notification targets externalTargets := globalEventNotifier.GetAllExternalTargets() if len(externalTargets) == 0 { - arnMsg += colorBold(fmt.Sprintf(getFormatStr(len(""), 1), "")) + return } + arnMsg := colorBlue("SQS ARNs: ") for queueArn := range externalTargets { arnMsg += colorBold(fmt.Sprintf(getFormatStr(len(queueArn), 1), queueArn)) } diff --git a/cmd/signature-v4-parser.go b/cmd/signature-v4-parser.go index 475361fa4..36060cfb8 100644 --- a/cmd/signature-v4-parser.go +++ b/cmd/signature-v4-parser.go @@ -69,9 +69,6 @@ func parseCredentialHeader(credElement string) (credentialHeader, APIErrorCode) if e != nil { return credentialHeader{}, ErrMalformedCredentialDate } - if credElements[2] == "" { - return credentialHeader{}, ErrMalformedCredentialRegion - } cred.scope.region = credElements[2] if credElements[3] != "s3" { return credentialHeader{}, ErrInvalidService diff --git a/cmd/signature-v4-parser_test.go b/cmd/signature-v4-parser_test.go index 6a59a4265..105eb8025 100644 --- a/cmd/signature-v4-parser_test.go +++ b/cmd/signature-v4-parser_test.go @@ -141,19 +141,6 @@ func TestParseCredentialHeader(t *testing.T) { expectedErrCode: ErrMalformedCredentialDate, }, // Test Case - 6. - // Test case with invalid region. - // region should a non empty string. - { - inputCredentialStr: generateCredentialStr( - "Z7IXGOO6BZ0REAN1Q26I", - UTCNow().Format(yyyymmdd), - "", - "ABCD", - "ABCD"), - expectedCredentials: credentialHeader{}, - expectedErrCode: ErrMalformedCredentialRegion, - }, - // Test Case - 7. // Test case with invalid service. // "s3" is the valid service string. { @@ -166,7 +153,7 @@ func TestParseCredentialHeader(t *testing.T) { expectedCredentials: credentialHeader{}, expectedErrCode: ErrInvalidService, }, - // Test Case - 8. + // Test Case - 7. // Test case with invalid request version. // "aws4_request" is the valid request version. { @@ -179,7 +166,7 @@ func TestParseCredentialHeader(t *testing.T) { expectedCredentials: credentialHeader{}, expectedErrCode: ErrInvalidRequestVersion, }, - // Test Case - 9. + // Test Case - 8. // Test case with right inputs. Expected to return a valid CredentialHeader. // "aws4_request" is the valid request version. { @@ -204,7 +191,7 @@ func TestParseCredentialHeader(t *testing.T) { actualCredential, actualErrCode := parseCredentialHeader(testCase.inputCredentialStr) // validating the credential fields. if testCase.expectedErrCode != actualErrCode { - t.Fatalf("Test %d: Expected the APIErrCode to be %d, got %d", i+1, testCase.expectedErrCode, actualErrCode) + t.Fatalf("Test %d: Expected the APIErrCode to be %s, got %s", i+1, errorCodeResponse[testCase.expectedErrCode].Code, errorCodeResponse[actualErrCode].Code) } if actualErrCode == ErrNone { validateCredentialfields(t, i+1, testCase.expectedCredentials, actualCredential) diff --git a/cmd/signature-v4-utils.go b/cmd/signature-v4-utils.go index 6ba30212d..d4c659cdc 100644 --- a/cmd/signature-v4-utils.go +++ b/cmd/signature-v4-utils.go @@ -68,7 +68,10 @@ func getContentSha256Cksum(r *http.Request) string { // isValidRegion - verify if incoming region value is valid with configured Region. func isValidRegion(reqRegion string, confRegion string) bool { - if confRegion == "" || confRegion == "US" { + if confRegion == "" { + return true + } + if confRegion == "US" { confRegion = globalMinioDefaultRegion } // Some older s3 clients set region as "US" instead of diff --git a/cmd/signature-v4-utils_test.go b/cmd/signature-v4-utils_test.go index 772f10312..07914eedb 100644 --- a/cmd/signature-v4-utils_test.go +++ b/cmd/signature-v4-utils_test.go @@ -80,7 +80,7 @@ func TestIsValidRegion(t *testing.T) { expectedResult bool }{ - {"", "", false}, + {"", "", true}, {globalMinioDefaultRegion, "", true}, {globalMinioDefaultRegion, "US", true}, {"us-west-1", "US", false}, diff --git a/cmd/signature-v4.go b/cmd/signature-v4.go index 157f2aef6..f71e17e36 100644 --- a/cmd/signature-v4.go +++ b/cmd/signature-v4.go @@ -175,7 +175,7 @@ func doesPolicySignatureV4Match(formValues http.Header) APIErrorCode { } // Get signing key. - signingKey := getSigningKey(cred.SecretKey, credHeader.scope.date, region) + signingKey := getSigningKey(cred.SecretKey, credHeader.scope.date, sRegion) // Get signature. newSignature := getSignature(signingKey, formValues.Get("Policy")) diff --git a/cmd/signature-v4_test.go b/cmd/signature-v4_test.go index 9c891dc87..5626ceeba 100644 --- a/cmd/signature-v4_test.go +++ b/cmd/signature-v4_test.go @@ -54,14 +54,7 @@ func TestDoesPolicySignatureMatch(t *testing.T) { }, expected: ErrInvalidAccessKeyID, }, - // (2) It should fail if the region is invalid. - { - 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. + // (2) It should fail with a bad signature. { form: http.Header{ "X-Amz-Credential": []string{fmt.Sprintf(credentialTemplate, accessKey, now.Format(yyyymmdd), globalMinioDefaultRegion)}, @@ -71,7 +64,7 @@ func TestDoesPolicySignatureMatch(t *testing.T) { }, expected: ErrSignatureDoesNotMatch, }, - // (4) It should succeed if everything is correct. + // (3) It should succeed if everything is correct. { form: http.Header{ "X-Amz-Credential": []string{ @@ -135,21 +128,7 @@ func TestDoesPresignedSignatureMatch(t *testing.T) { region: "us-west-1", expected: ErrInvalidAccessKeyID, }, - // (2) Should fail with an invalid region. - { - queryParams: map[string]string{ - "X-Amz-Algorithm": signV4Algorithm, - "X-Amz-Date": now.Format(iso8601Format), - "X-Amz-Expires": "60", - "X-Amz-Signature": "badsignature", - "X-Amz-SignedHeaders": "host;x-amz-content-sha256;x-amz-date", - "X-Amz-Credential": fmt.Sprintf(credentialTemplate, accessKeyID, now.Format(yyyymmdd), "us-west-1"), - "X-Amz-Content-Sha256": payloadSHA256, - }, - region: globalMinioDefaultRegion, - expected: ErrInvalidRegion, - }, - // (3) Should NOT fail with an invalid region if it doesn't verify it. + // (2) Should NOT fail with an invalid region if it doesn't verify it. { queryParams: map[string]string{ "X-Amz-Algorithm": signV4Algorithm, @@ -163,7 +142,7 @@ func TestDoesPresignedSignatureMatch(t *testing.T) { region: "us-west-1", expected: ErrUnsignedHeaders, }, - // (4) Should fail to extract headers if the host header is not signed. + // (3) Should fail to extract headers if the host header is not signed. { queryParams: map[string]string{ "X-Amz-Algorithm": signV4Algorithm, @@ -177,7 +156,7 @@ func TestDoesPresignedSignatureMatch(t *testing.T) { region: region, expected: ErrUnsignedHeaders, }, - // (5) Should give an expired request if it has expired. + // (4) Should give an expired request if it has expired. { queryParams: map[string]string{ "X-Amz-Algorithm": signV4Algorithm, @@ -195,7 +174,7 @@ func TestDoesPresignedSignatureMatch(t *testing.T) { region: region, expected: ErrExpiredPresignRequest, }, - // (6) Should error if the signature is incorrect. + // (5) Should error if the signature is incorrect. { queryParams: map[string]string{ "X-Amz-Algorithm": signV4Algorithm, @@ -213,7 +192,7 @@ func TestDoesPresignedSignatureMatch(t *testing.T) { region: region, expected: ErrSignatureDoesNotMatch, }, - // (7) Should error if the request is not ready yet, ie X-Amz-Date is in the future. + // (6) Should error if the request is not ready yet, ie X-Amz-Date is in the future. { queryParams: map[string]string{ "X-Amz-Algorithm": signV4Algorithm, @@ -231,7 +210,7 @@ func TestDoesPresignedSignatureMatch(t *testing.T) { region: region, expected: ErrRequestNotReadyYet, }, - // (8) Should not error with invalid region instead, call should proceed + // (7) Should not error with invalid region instead, call should proceed // with sigature does not match. { queryParams: map[string]string{ @@ -250,7 +229,7 @@ func TestDoesPresignedSignatureMatch(t *testing.T) { region: "", expected: ErrSignatureDoesNotMatch, }, - // (9) Should error with signature does not match. But handles + // (8) Should error with signature does not match. But handles // query params which do not precede with "x-amz-" header. { queryParams: map[string]string{ @@ -270,7 +249,7 @@ func TestDoesPresignedSignatureMatch(t *testing.T) { region: "", expected: ErrSignatureDoesNotMatch, }, - // (10) Should error with unsigned headers. + // (9) Should error with unsigned headers. { queryParams: map[string]string{ "X-Amz-Algorithm": signV4Algorithm, From cae4683971cca3bd19783ac69040307d1251d50b Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 16 May 2017 07:19:17 -0700 Subject: [PATCH 27/80] Make clearing of stale debug lock info independent of deleting map entry of lock itself. (#4353) This is believed to address issue #4337 where stale information for debug locks in shown. --- cmd/namespace-lock.go | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/cmd/namespace-lock.go b/cmd/namespace-lock.go index 75bce13ee..9aa470825 100644 --- a/cmd/namespace-lock.go +++ b/cmd/namespace-lock.go @@ -240,14 +240,12 @@ func (n *nsLockMap) ForceUnlock(volume, path string) { if _, found := n.lockMap[param]; found { // Remove lock from the map. delete(n.lockMap, param) - - // delete the lock state entry for given - // pair. - err := n.deleteLockInfoEntryForVolumePath(param) - if err != nil { - errorIf(err, "Failed to delete lock info entry") - } } + + // delete the lock state entry for given + // pair. Ignore error as there + // is no way to report it back + n.deleteLockInfoEntryForVolumePath(param) } // lockInstance - frontend/top-level interface for namespace locks. From 8975da4e84925095e4a5089fb2127146feb4cc41 Mon Sep 17 00:00:00 2001 From: Aditya Manthramurthy Date: Tue, 16 May 2017 14:21:52 -0700 Subject: [PATCH 28/80] Add new ReadFileWithVerify storage-layer API (#4349) This is an enhancement to the XL/distributed-XL mode. FS mode is unaffected. The ReadFileWithVerify storage-layer call is similar to ReadFile with the additional functionality of performing bit-rot checking. It accepts additional parameters for a hashing algorithm to use and the expected hex-encoded hash string. This patch provides significant performance improvement because: 1. combines the step of reading the file (during erasure-decoding/reconstruction) with bit-rot verification; 2. limits the number of file-reads; and 3. avoids transferring the file over the network for bit-rot verification. ReadFile API is implemented as ReadFileWithVerify with empty hashing arguments. Credits to AB and Harsha for the algorithmic improvement. Fixes #4236. --- cmd/erasure-createfile.go | 4 +- cmd/erasure-healfile.go | 4 +- cmd/erasure-readfile.go | 104 ++++++++++++------------- cmd/erasure-readfile_test.go | 6 ++ cmd/erasure-utils.go | 23 ++++-- cmd/naughty-disk_test.go | 9 +++ cmd/posix.go | 86 ++++++++++++++++++--- cmd/posix_test.go | 113 ++++++++++++++++++++++++++++ cmd/retry-storage.go | 17 +++++ cmd/retry-storage_test.go | 27 +++++++ cmd/storage-errors.go | 23 +++++- cmd/storage-interface.go | 2 + cmd/storage-rpc-client.go | 29 +++++++ cmd/storage-rpc-client_test.go | 21 ++++++ cmd/storage-rpc-server-datatypes.go | 25 ++++++ cmd/storage-rpc-server.go | 20 +++++ cmd/xl-v1-metadata.go | 38 +++++++--- cmd/xl-v1-object.go | 2 +- cmd/xl-v1-utils.go | 4 +- cmd/xl-v1-utils_test.go | 2 +- 20 files changed, 471 insertions(+), 88 deletions(-) diff --git a/cmd/erasure-createfile.go b/cmd/erasure-createfile.go index 807684210..db300cf39 100644 --- a/cmd/erasure-createfile.go +++ b/cmd/erasure-createfile.go @@ -28,7 +28,9 @@ import ( // erasureCreateFile - writes an entire stream by erasure coding to // all the disks, writes also calculate individual block's checksum // for future bit-rot protection. -func erasureCreateFile(disks []StorageAPI, volume, path string, reader io.Reader, allowEmpty bool, blockSize int64, dataBlocks int, parityBlocks int, algo string, writeQuorum int) (bytesWritten int64, checkSums []string, err error) { +func erasureCreateFile(disks []StorageAPI, volume, path string, reader io.Reader, allowEmpty bool, blockSize int64, + dataBlocks, parityBlocks int, algo HashAlgo, writeQuorum int) (bytesWritten int64, checkSums []string, err error) { + // Allocated blockSized buffer for reading from incoming stream. buf := make([]byte, blockSize) diff --git a/cmd/erasure-healfile.go b/cmd/erasure-healfile.go index 5d029ad5c..2d0e852cb 100644 --- a/cmd/erasure-healfile.go +++ b/cmd/erasure-healfile.go @@ -19,7 +19,9 @@ package cmd import "encoding/hex" // Heals the erasure coded file. reedsolomon.Reconstruct() is used to reconstruct the missing parts. -func erasureHealFile(latestDisks []StorageAPI, outDatedDisks []StorageAPI, volume, path, healBucket, healPath string, size int64, blockSize int64, dataBlocks int, parityBlocks int, algo string) (checkSums []string, err error) { +func erasureHealFile(latestDisks []StorageAPI, outDatedDisks []StorageAPI, volume, path, healBucket, healPath string, + size, blockSize int64, dataBlocks, parityBlocks int, algo HashAlgo) (checkSums []string, err error) { + var offset int64 remainingSize := size diff --git a/cmd/erasure-readfile.go b/cmd/erasure-readfile.go index 682522c36..bf03f033c 100644 --- a/cmd/erasure-readfile.go +++ b/cmd/erasure-readfile.go @@ -17,7 +17,6 @@ package cmd import ( - "encoding/hex" "errors" "io" "sync" @@ -111,7 +110,9 @@ func getReadDisks(orderedDisks []StorageAPI, index int, dataBlocks int) (readDis } // parallelRead - reads chunks in parallel from the disks specified in []readDisks. -func parallelRead(volume, path string, readDisks []StorageAPI, orderedDisks []StorageAPI, enBlocks [][]byte, blockOffset int64, curChunkSize int64, bitRotVerify func(diskIndex int) bool, pool *bpool.BytePool) { +func parallelRead(volume, path string, readDisks, orderedDisks []StorageAPI, enBlocks [][]byte, + blockOffset, curChunkSize int64, brVerifiers []bitRotVerifier, pool *bpool.BytePool) { + // WaitGroup to synchronise the read go-routines. wg := &sync.WaitGroup{} @@ -125,11 +126,15 @@ func parallelRead(volume, path string, readDisks []StorageAPI, orderedDisks []St go func(index int) { defer wg.Done() - // Verify bit rot for the file on this disk. - if !bitRotVerify(index) { - // So that we don't read from this disk for the next block. - orderedDisks[index] = nil - return + // evaluate if we need to perform bit-rot checking + needBitRotVerification := true + if brVerifiers[index].isVerified { + needBitRotVerification = false + // if file has bit-rot, do not reuse disk + if brVerifiers[index].hasBitRot { + orderedDisks[index] = nil + return + } } buf, err := pool.Get() @@ -140,7 +145,25 @@ func parallelRead(volume, path string, readDisks []StorageAPI, orderedDisks []St } buf = buf[:curChunkSize] - _, err = readDisks[index].ReadFile(volume, path, blockOffset, buf) + if needBitRotVerification { + _, err = readDisks[index].ReadFileWithVerify( + volume, path, blockOffset, buf, + brVerifiers[index].algo, + brVerifiers[index].checkSum) + } else { + _, err = readDisks[index].ReadFile(volume, path, + blockOffset, buf) + } + + // if bit-rot verification was done, store the + // result of verification so we can skip + // re-doing it next time + if needBitRotVerification { + brVerifiers[index].isVerified = true + _, ok := err.(hashMismatchError) + brVerifiers[index].hasBitRot = ok + } + if err != nil { orderedDisks[index] = nil return @@ -153,12 +176,16 @@ func parallelRead(volume, path string, readDisks []StorageAPI, orderedDisks []St wg.Wait() } -// erasureReadFile - read bytes from erasure coded files and writes to given writer. -// Erasure coded files are read block by block as per given erasureInfo and data chunks -// are decoded into a data block. Data block is trimmed for given offset and length, -// then written to given writer. This function also supports bit-rot detection by -// verifying checksum of individual block's checksum. -func erasureReadFile(writer io.Writer, disks []StorageAPI, volume string, path string, offset int64, length int64, totalLength int64, blockSize int64, dataBlocks int, parityBlocks int, checkSums []string, algo string, pool *bpool.BytePool) (int64, error) { +// erasureReadFile - read bytes from erasure coded files and writes to +// given writer. Erasure coded files are read block by block as per +// given erasureInfo and data chunks are decoded into a data +// block. Data block is trimmed for given offset and length, then +// written to given writer. This function also supports bit-rot +// detection by verifying checksum of individual block's checksum. +func erasureReadFile(writer io.Writer, disks []StorageAPI, volume, path string, + offset, length, totalLength, blockSize int64, dataBlocks, parityBlocks int, + checkSums []string, algo HashAlgo, pool *bpool.BytePool) (int64, error) { + // Offset and length cannot be negative. if offset < 0 || length < 0 { return 0, traceError(errUnexpected) @@ -169,27 +196,15 @@ func erasureReadFile(writer io.Writer, disks []StorageAPI, volume string, path s return 0, traceError(errUnexpected) } - // chunkSize is the amount of data that needs to be read from each disk at a time. + // chunkSize is the amount of data that needs to be read from + // each disk at a time. chunkSize := getChunkSize(blockSize, dataBlocks) - // bitRotVerify verifies if the file on a particular disk doesn't have bitrot - // by verifying the hash of the contents of the file. - bitRotVerify := func() func(diskIndex int) bool { - verified := make([]bool, len(disks)) - // Return closure so that we have reference to []verified and - // not recalculate the hash on it every time the function is - // called for the same disk. - return func(diskIndex int) bool { - if verified[diskIndex] { - // Already validated. - return true - } - // Is this a valid block? - isValid := isValidBlock(disks[diskIndex], volume, path, checkSums[diskIndex], algo) - verified[diskIndex] = isValid - return isValid - } - }() + brVerifiers := make([]bitRotVerifier, len(disks)) + for i := range brVerifiers { + brVerifiers[i].algo = algo + brVerifiers[i].checkSum = checkSums[i] + } // Total bytes written to writer var bytesWritten int64 @@ -241,7 +256,7 @@ func erasureReadFile(writer io.Writer, disks []StorageAPI, volume string, path s return bytesWritten, err } // Issue a parallel read across the disks specified in readDisks. - parallelRead(volume, path, readDisks, disks, enBlocks, blockOffset, curChunkSize, bitRotVerify, pool) + parallelRead(volume, path, readDisks, disks, enBlocks, blockOffset, curChunkSize, brVerifiers, pool) if isSuccessDecodeBlocks(enBlocks, dataBlocks) { // If enough blocks are available to do rs.Reconstruct() break @@ -299,27 +314,6 @@ func erasureReadFile(writer io.Writer, disks []StorageAPI, volume string, path s return bytesWritten, nil } -// isValidBlock - calculates the checksum hash for the block and -// validates if its correct returns true for valid cases, false otherwise. -func isValidBlock(disk StorageAPI, volume, path, checkSum, checkSumAlgo string) (ok bool) { - // Disk is not available, not a valid block. - if disk == nil { - return false - } - // Checksum not available, not a valid block. - if checkSum == "" { - return false - } - // Read everything for a given block and calculate hash. - hashWriter := newHash(checkSumAlgo) - hashBytes, err := hashSum(disk, volume, path, hashWriter) - if err != nil { - errorIf(err, "Unable to calculate checksum %s/%s", volume, path) - return false - } - return hex.EncodeToString(hashBytes) == checkSum -} - // decodeData - decode encoded blocks. func decodeData(enBlocks [][]byte, dataBlocks, parityBlocks int) error { // Initialized reedsolomon. diff --git a/cmd/erasure-readfile_test.go b/cmd/erasure-readfile_test.go index 1fe1d563a..6d7fef8ad 100644 --- a/cmd/erasure-readfile_test.go +++ b/cmd/erasure-readfile_test.go @@ -213,6 +213,12 @@ func (r ReadDiskDown) ReadFile(volume string, path string, offset int64, buf []b return 0, errFaultyDisk } +func (r ReadDiskDown) ReadFileWithVerify(volume string, path string, offset int64, buf []byte, + algo HashAlgo, expectedHash string) (n int64, err error) { + + return 0, errFaultyDisk +} + func TestErasureReadFileDiskFail(t *testing.T) { // Initialize environment needed for the test. dataBlocks := 7 diff --git a/cmd/erasure-utils.go b/cmd/erasure-utils.go index 6fe01c499..7ce616f83 100644 --- a/cmd/erasure-utils.go +++ b/cmd/erasure-utils.go @@ -29,7 +29,7 @@ import ( ) // newHashWriters - inititialize a slice of hashes for the disk count. -func newHashWriters(diskCount int, algo string) []hash.Hash { +func newHashWriters(diskCount int, algo HashAlgo) []hash.Hash { hashWriters := make([]hash.Hash, diskCount) for index := range hashWriters { hashWriters[index] = newHash(algo) @@ -38,13 +38,13 @@ func newHashWriters(diskCount int, algo string) []hash.Hash { } // newHash - gives you a newly allocated hash depending on the input algorithm. -func newHash(algo string) (h hash.Hash) { +func newHash(algo HashAlgo) (h hash.Hash) { switch algo { - case sha256Algo: + case HashSha256: // sha256 checksum specially on ARM64 platforms or whenever // requested as dictated by `xl.json` entry. h = sha256.New() - case blake2bAlgo: + case HashBlake2b: // ignore the error, because New512 without a key never fails // New512 only returns a non-nil error, if the length of the passed // key > 64 bytes - but we use blake2b as hash function (no key) @@ -71,7 +71,7 @@ var hashBufferPool = sync.Pool{ // hashSum calculates the hash of the entire path and returns. func hashSum(disk StorageAPI, volume, path string, writer hash.Hash) ([]byte, error) { - // Fetch staging a new staging buffer from the pool. + // Fetch a new staging buffer from the pool. bufp := hashBufferPool.Get().(*[]byte) defer hashBufferPool.Put(bufp) @@ -207,3 +207,16 @@ func copyBuffer(writer io.Writer, disk StorageAPI, volume string, path string, b // Success. return nil } + +// bitRotVerifier - type representing bit-rot verification process for +// a single under-lying object (currently whole files) +type bitRotVerifier struct { + // has the bit-rot verification been done? + isVerified bool + // is the data free of bit-rot? + hasBitRot bool + // hashing algorithm + algo HashAlgo + // hex-encoded expected raw-hash value + checkSum string +} diff --git a/cmd/naughty-disk_test.go b/cmd/naughty-disk_test.go index 8b7e62845..0e19c1cc5 100644 --- a/cmd/naughty-disk_test.go +++ b/cmd/naughty-disk_test.go @@ -122,6 +122,15 @@ func (d *naughtyDisk) ReadFile(volume string, path string, offset int64, buf []b return d.disk.ReadFile(volume, path, offset, buf) } +func (d *naughtyDisk) ReadFileWithVerify(volume, path string, offset int64, + buf []byte, algo HashAlgo, expectedHash string) (n int64, err error) { + + if err := d.calcError(); err != nil { + return 0, err + } + return d.disk.ReadFileWithVerify(volume, path, offset, buf, algo, expectedHash) +} + func (d *naughtyDisk) PrepareFile(volume, path string, length int64) error { if err := d.calcError(); err != nil { return err diff --git a/cmd/posix.go b/cmd/posix.go index 84fc25c68..fb7b025c9 100644 --- a/cmd/posix.go +++ b/cmd/posix.go @@ -18,6 +18,8 @@ package cmd import ( "bytes" + "encoding/hex" + "hash" "io" "io/ioutil" "os" @@ -512,11 +514,30 @@ func (s *posix) ReadAll(volume, path string) (buf []byte, err error) { // number of bytes copied. The error is EOF only if no bytes were // read. On return, n == len(buf) if and only if err == nil. n == 0 // for io.EOF. +// // If an EOF happens after reading some but not all the bytes, // ReadFull returns ErrUnexpectedEOF. -// Additionally ReadFile also starts reading from an offset. -// ReadFile symantics are same as io.ReadFull -func (s *posix) ReadFile(volume string, path string, offset int64, buf []byte) (n int64, err error) { +// +// Additionally ReadFile also starts reading from an offset. ReadFile +// semantics are same as io.ReadFull. +func (s *posix) ReadFile(volume, path string, offset int64, buf []byte) (n int64, err error) { + + return s.ReadFileWithVerify(volume, path, offset, buf, "", "") +} + +// ReadFileWithVerify is the same as ReadFile but with hashsum +// verification: the operation will fail if the hash verification +// fails. +// +// The `expectedHash` is the expected hex-encoded hash string for +// verification. With an empty expected hash string, hash verification +// is skipped. An empty HashAlgo defaults to `blake2b`. +// +// The function takes care to minimize the number of disk read +// operations. +func (s *posix) ReadFileWithVerify(volume, path string, offset int64, buf []byte, + algo HashAlgo, expectedHash string) (n int64, err error) { + defer func() { if err == syscall.EIO { atomic.AddInt32(&s.ioErrCount, 1) @@ -571,19 +592,66 @@ func (s *posix) ReadFile(volume string, path string, offset int64, buf []byte) ( return 0, err } - // Verify if its not a regular file, since subsequent Seek is undefined. + // Verify it is a regular file, otherwise subsequent Seek is + // undefined. if !st.Mode().IsRegular() { return 0, errIsNotRegular } - // Seek to requested offset. - _, err = file.Seek(offset, os.SEEK_SET) - if err != nil { + // If expected hash string is empty hash verification is + // skipped. + needToHash := expectedHash != "" + var hasher hash.Hash + + if needToHash { + // If the hashing algo is invalid, return an error. + if !isValidHashAlgo(algo) { + return 0, errBitrotHashAlgoInvalid + } + + // Compute hash of object from start to the byte at + // (offset - 1), and as a result of this read, seek to + // `offset`. + hasher = newHash(algo) + if offset > 0 { + _, err = io.CopyN(hasher, file, offset) + if err != nil { + return 0, err + } + } + } else { + // Seek to requested offset. + _, err = file.Seek(offset, os.SEEK_SET) + if err != nil { + return 0, err + } + } + + // Read until buffer is full. + m, err := io.ReadFull(file, buf) + if err == io.EOF { return 0, err } - // Read full until buffer. - m, err := io.ReadFull(file, buf) + if needToHash { + // Continue computing hash with buf. + _, err = hasher.Write(buf) + if err != nil { + return 0, err + } + + // Continue computing hash until end of file. + _, err = io.Copy(hasher, file) + if err != nil { + return 0, err + } + + // Verify the computed hash. + computedHash := hex.EncodeToString(hasher.Sum(nil)) + if computedHash != expectedHash { + return 0, hashMismatchError{expectedHash, computedHash} + } + } // Success. return int64(m), err diff --git a/cmd/posix_test.go b/cmd/posix_test.go index e7fb98927..3ef44dc04 100644 --- a/cmd/posix_test.go +++ b/cmd/posix_test.go @@ -18,6 +18,8 @@ package cmd import ( "bytes" + "crypto/sha256" + "encoding/hex" "io" "io/ioutil" "os" @@ -26,6 +28,8 @@ import ( "strings" "syscall" "testing" + + "golang.org/x/crypto/blake2b" ) // creates a temp dir and sets up posix layer. @@ -1017,6 +1021,115 @@ func TestPosixReadFile(t *testing.T) { } } +// TestPosixReadFileWithVerify - tests the posix level +// ReadFileWithVerify API. Only tests hashing related +// functionality. Other functionality is tested with +// TestPosixReadFile. +func TestPosixReadFileWithVerify(t *testing.T) { + // create posix test setup + posixStorage, path, err := newPosixTestSetup() + if err != nil { + t.Fatalf("Unable to create posix test setup, %s", err) + } + defer removeAll(path) + + volume := "success-vol" + // Setup test environment. + if err = posixStorage.MakeVol(volume); err != nil { + t.Fatalf("Unable to create volume, %s", err) + } + + blakeHash := func(s string) string { + k := blake2b.Sum512([]byte(s)) + return hex.EncodeToString(k[:]) + } + + sha256Hash := func(s string) string { + k := sha256.Sum256([]byte(s)) + return hex.EncodeToString(k[:]) + } + + testCases := []struct { + fileName string + offset int64 + bufSize int + algo HashAlgo + expectedHash string + + expectedBuf []byte + expectedErr error + }{ + // Hash verification is skipped with empty expected + // hash - 1 + { + "myobject", 0, 5, HashBlake2b, "", + []byte("Hello"), nil, + }, + // Hash verification failure case - 2 + { + "myobject", 0, 5, HashBlake2b, "a", + []byte(""), + hashMismatchError{"a", blakeHash("Hello, world!")}, + }, + // Hash verification success with full content requested - 3 + { + "myobject", 0, 13, HashBlake2b, blakeHash("Hello, world!"), + []byte("Hello, world!"), nil, + }, + // Hash verification success with full content and Sha256 - 4 + { + "myobject", 0, 13, HashSha256, sha256Hash("Hello, world!"), + []byte("Hello, world!"), nil, + }, + // Hash verification success with partial content requested - 5 + { + "myobject", 7, 4, HashBlake2b, blakeHash("Hello, world!"), + []byte("worl"), nil, + }, + // Hash verification success with partial content and Sha256 - 6 + { + "myobject", 7, 4, HashSha256, sha256Hash("Hello, world!"), + []byte("worl"), nil, + }, + // Empty hash-algo returns error - 7 + { + "myobject", 7, 4, "", blakeHash("Hello, world!"), + []byte("worl"), errBitrotHashAlgoInvalid, + }, + // Empty content hash verification with empty + // hash-algo algo returns error - 8 + { + "myobject", 7, 0, "", blakeHash("Hello, world!"), + []byte(""), errBitrotHashAlgoInvalid, + }, + } + + // Create file used in testcases + err = posixStorage.AppendFile(volume, "myobject", []byte("Hello, world!")) + if err != nil { + t.Fatalf("Failure in test setup: %v\n", err) + } + + // Validate each test case. + for i, testCase := range testCases { + var n int64 + // Common read buffer. + var buf = make([]byte, testCase.bufSize) + n, err = posixStorage.ReadFileWithVerify(volume, testCase.fileName, testCase.offset, buf, testCase.algo, testCase.expectedHash) + + switch { + case err == nil && testCase.expectedErr != nil: + t.Errorf("Test %d: Expected error %v but got none.", i+1, testCase.expectedErr) + case err == nil && n != int64(testCase.bufSize): + t.Errorf("Test %d: %d bytes were expected, but %d were written", i+1, testCase.bufSize, n) + case err == nil && !bytes.Equal(testCase.expectedBuf, buf): + t.Errorf("Test %d: Expected bytes: %v, but got: %v", i+1, testCase.expectedBuf, buf) + case err != nil && err != testCase.expectedErr: + t.Errorf("Test %d: Expected error: %v, but got: %v", i+1, testCase.expectedErr, err) + } + } +} + // TestPosix posix.AppendFile() func TestPosixAppendFile(t *testing.T) { // create posix test setup diff --git a/cmd/retry-storage.go b/cmd/retry-storage.go index f30170ed8..ea88460c3 100644 --- a/cmd/retry-storage.go +++ b/cmd/retry-storage.go @@ -178,6 +178,23 @@ func (f retryStorage) ReadFile(volume, path string, offset int64, buffer []byte) return m, err } +// ReadFileWithVerify - a retryable implementation of reading at +// offset from a file with verification. +func (f retryStorage) ReadFileWithVerify(volume, path string, offset int64, buffer []byte, + algo HashAlgo, expectedHash string) (m int64, err error) { + + m, err = f.remoteStorage.ReadFileWithVerify(volume, path, offset, buffer, + algo, expectedHash) + if err == errDiskNotFound { + err = f.reInit() + if err == nil { + return f.remoteStorage.ReadFileWithVerify(volume, path, + offset, buffer, algo, expectedHash) + } + } + return m, err +} + // ListDir - a retryable implementation of listing directory entries. func (f retryStorage) ListDir(volume, path string) (entries []string, err error) { entries, err = f.remoteStorage.ListDir(volume, path) diff --git a/cmd/retry-storage_test.go b/cmd/retry-storage_test.go index 5642910f8..2e2b227e8 100644 --- a/cmd/retry-storage_test.go +++ b/cmd/retry-storage_test.go @@ -18,6 +18,8 @@ package cmd import ( "bytes" + "crypto/sha256" + "encoding/hex" "reflect" "testing" "time" @@ -290,6 +292,31 @@ func TestRetryStorage(t *testing.T) { if n, err = disk.ReadFile("existent", "path", 7, buf2); err != nil { t.Fatal(err) } + if err != nil { + t.Error("Error in ReadFile", err) + } + if n != 5 { + t.Fatalf("Expected 5, got %d", n) + } + if !bytes.Equal(buf2, []byte("World")) { + t.Fatalf("Expected `World`, got %s", string(buf2)) + } + } + + sha256Hash := func(s string) string { + k := sha256.Sum256([]byte(s)) + return hex.EncodeToString(k[:]) + } + for _, disk := range storageDisks { + var buf2 = make([]byte, 5) + var n int64 + if n, err = disk.ReadFileWithVerify("existent", "path", 7, buf2, + HashSha256, sha256Hash("Hello, World")); err != nil { + t.Fatal(err) + } + if err != nil { + t.Error("Error in ReadFileWithVerify", err) + } if n != 5 { t.Fatalf("Expected 5, got %d", n) } diff --git a/cmd/storage-errors.go b/cmd/storage-errors.go index 40c7beed0..2df4b43eb 100644 --- a/cmd/storage-errors.go +++ b/cmd/storage-errors.go @@ -16,7 +16,10 @@ package cmd -import "errors" +import ( + "errors" + "fmt" +) // errUnexpected - unexpected error, requires manual intervention. var errUnexpected = errors.New("Unexpected error, please report this issue at https://github.com/minio/minio/issues") @@ -68,3 +71,21 @@ var errVolumeAccessDenied = errors.New("volume access denied") // errVolumeAccessDenied - cannot access file, insufficient permissions. var errFileAccessDenied = errors.New("file access denied") + +// errBitrotHashAlgoInvalid - the algo for bit-rot hash +// verification is empty or invalid. +var errBitrotHashAlgoInvalid = errors.New("bit-rot hash algorithm is invalid") + +// hashMisMatchError - represents a bit-rot hash verification failure +// error. +type hashMismatchError struct { + expected string + computed string +} + +// error method for the hashMismatchError +func (h hashMismatchError) Error() string { + return fmt.Sprintf( + "Bitrot verification mismatch - expected %v, received %v", + h.expected, h.computed) +} diff --git a/cmd/storage-interface.go b/cmd/storage-interface.go index d9996bbc4..6e6dbb05f 100644 --- a/cmd/storage-interface.go +++ b/cmd/storage-interface.go @@ -37,6 +37,8 @@ type StorageAPI interface { // File operations. ListDir(volume, dirPath string) ([]string, error) ReadFile(volume string, path string, offset int64, buf []byte) (n int64, err error) + ReadFileWithVerify(volume string, path string, offset int64, buf []byte, + algo HashAlgo, expectedHash string) (n int64, err error) PrepareFile(volume string, path string, len int64) (err error) AppendFile(volume string, path string, buf []byte) (err error) RenameFile(srcVolume, srcPath, dstVolume, dstPath string) error diff --git a/cmd/storage-rpc-client.go b/cmd/storage-rpc-client.go index ea2b72e43..14dab823c 100644 --- a/cmd/storage-rpc-client.go +++ b/cmd/storage-rpc-client.go @@ -262,6 +262,35 @@ func (n *networkStorage) ReadFile(volume string, path string, offset int64, buff return int64(len(result)), toStorageErr(err) } +// ReadFileWithVerify - reads a file at remote path and fills the buffer. +func (n *networkStorage) ReadFileWithVerify(volume string, path string, offset int64, + buffer []byte, algo HashAlgo, expectedHash string) (m int64, err error) { + + defer func() { + if r := recover(); r != nil { + // Recover any panic from allocation, and return error. + err = bytes.ErrTooLarge + } + }() // Do not crash the server. + + var result []byte + err = n.rpcClient.Call("Storage.ReadFileWithVerifyHandler", + &ReadFileWithVerifyArgs{ + Vol: volume, + Path: path, + Offset: offset, + Buffer: buffer, + Algo: algo, + ExpectedHash: expectedHash, + }, &result) + + // Copy results to buffer. + copy(buffer, result) + + // Return length of result, err if any. + return int64(len(result)), toStorageErr(err) +} + // ListDir - list all entries at prefix. func (n *networkStorage) ListDir(volume, path string) (entries []string, err error) { if err = n.rpcClient.Call("Storage.ListDirHandler", &ListDirArgs{ diff --git a/cmd/storage-rpc-client_test.go b/cmd/storage-rpc-client_test.go index 8cb6c1768..a3bf44f52 100644 --- a/cmd/storage-rpc-client_test.go +++ b/cmd/storage-rpc-client_test.go @@ -18,6 +18,7 @@ package cmd import ( "bytes" + "encoding/hex" "errors" "fmt" "io" @@ -25,6 +26,8 @@ import ( "net/rpc" "runtime" "testing" + + "golang.org/x/crypto/blake2b" ) // Tests the construction of canonical string by the @@ -387,6 +390,24 @@ func (s *TestRPCStorageSuite) testRPCStorageFileOps(t *testing.T) { if !bytes.Equal(buf[4:9], buf1) { t.Errorf("Expected %s, got %s", string(buf[4:9]), string(buf1)) } + + blakeHash := func(s string) string { + k := blake2b.Sum512([]byte(s)) + return hex.EncodeToString(k[:]) + } + buf2 := make([]byte, 2) + n, err = storageDisk.ReadFileWithVerify("myvol", "file1", 1, + buf2, HashBlake2b, blakeHash(string(buf))) + if err != nil { + t.Error("Error in ReadFileWithVerify", err) + } + if n != 2 { + t.Errorf("Expected `2`, got %d", n) + } + if !bytes.Equal(buf[1:3], buf2) { + t.Errorf("Expected %s, got %s", string(buf[1:3]), string(buf2)) + } + err = storageDisk.RenameFile("myvol", "file1", "myvol", "file2") if err != nil { t.Error("Unable to initiate RenameFile", err) diff --git a/cmd/storage-rpc-server-datatypes.go b/cmd/storage-rpc-server-datatypes.go index e3dd9bf9a..c426798b7 100644 --- a/cmd/storage-rpc-server-datatypes.go +++ b/cmd/storage-rpc-server-datatypes.go @@ -61,6 +61,31 @@ type ReadFileArgs struct { Buffer []byte } +// ReadFileWithVerifyArgs represents read file RPC arguments. +type ReadFileWithVerifyArgs struct { + // Authentication token generated by Login. + AuthRPCArgs + + // Name of the volume. + Vol string + + // Name of the path. + Path string + + // Starting offset to start reading into Buffer. + Offset int64 + + // Data buffer read from the path at offset. + Buffer []byte + + // Algorithm used in bit-rot hash computation. + Algo HashAlgo + + // Stored hash value (hex-encoded) used to compare with + // computed value. + ExpectedHash string +} + // PrepareFileArgs represents append file RPC arguments. type PrepareFileArgs struct { // Authentication token generated by Login. diff --git a/cmd/storage-rpc-server.go b/cmd/storage-rpc-server.go index 89e88a661..ee4d968fb 100644 --- a/cmd/storage-rpc-server.go +++ b/cmd/storage-rpc-server.go @@ -160,6 +160,26 @@ func (s *storageServer) ReadFileHandler(args *ReadFileArgs, reply *[]byte) (err return err } +// ReadFileWithVerifyHandler - read file with verify handler is rpc wrapper to read file with verify. +func (s *storageServer) ReadFileWithVerifyHandler(args *ReadFileWithVerifyArgs, reply *[]byte) (err error) { + if err = args.IsAuthenticated(); err != nil { + return err + } + + var n int64 + n, err = s.storage.ReadFileWithVerify(args.Vol, args.Path, args.Offset, args.Buffer, + args.Algo, args.ExpectedHash) + // Sending an error over the rpc layer, would cause unmarshalling to fail. In situations + // when we have short read i.e `io.ErrUnexpectedEOF` treat it as good condition and copy + // the buffer properly. + if err == io.ErrUnexpectedEOF { + // Reset to nil as good condition. + err = nil + } + *reply = args.Buffer[0:n] + return err +} + // PrepareFileHandler - prepare file handler is rpc wrapper to prepare file. func (s *storageServer) PrepareFileHandler(args *PrepareFileArgs, reply *AuthRPCReply) error { if err := args.IsAuthenticated(); err != nil { diff --git a/cmd/xl-v1-metadata.go b/cmd/xl-v1-metadata.go index 429cc8f8e..588df3213 100644 --- a/cmd/xl-v1-metadata.go +++ b/cmd/xl-v1-metadata.go @@ -49,19 +49,33 @@ func (t byObjectPartNumber) Less(i, j int) bool { return t[i].Number < t[j].Numb // checkSumInfo - carries checksums of individual scattered parts per disk. type checkSumInfo struct { - Name string `json:"name"` - Algorithm string `json:"algorithm"` - Hash string `json:"hash"` + Name string `json:"name"` + Algorithm HashAlgo `json:"algorithm"` + Hash string `json:"hash"` } -// Various algorithms supported by bit-rot protection feature. +// HashAlgo - represents a supported hashing algorithm for bitrot +// verification. +type HashAlgo string + const ( - // "sha256" is specifically used on arm64 bit platforms. - sha256Algo = "sha256" - // Rest of the platforms default to blake2b. - blake2bAlgo = "blake2b" + // HashBlake2b represents the Blake 2b hashing algorithm + HashBlake2b HashAlgo = "blake2b" + // HashSha256 represents the SHA256 hashing algorithm + HashSha256 HashAlgo = "sha256" ) +// isValidHashAlgo - function that checks if the hash algorithm is +// valid (known and used). +func isValidHashAlgo(algo HashAlgo) bool { + switch algo { + case HashSha256, HashBlake2b: + return true + default: + return false + } +} + // Constant indicates current bit-rot algo used when creating objects. // Depending on the architecture we are choosing a different checksum. var bitRotAlgo = getDefaultBitRotAlgo() @@ -70,7 +84,7 @@ var bitRotAlgo = getDefaultBitRotAlgo() // Currently this function defaults to "blake2b" as the preferred // checksum algorithm on all architectures except ARM64. On ARM64 // we use sha256 (optimized using sha2 instructions of ARM NEON chip). -func getDefaultBitRotAlgo() string { +func getDefaultBitRotAlgo() HashAlgo { switch runtime.GOARCH { case "arm64": // As a special case for ARM64 we use an optimized @@ -79,17 +93,17 @@ func getDefaultBitRotAlgo() string { // This would also allows erasure coded writes // on ARM64 servers to be on-par with their // counter-part X86_64 servers. - return sha256Algo + return HashSha256 default: // Default for all other architectures we use blake2b. - return blake2bAlgo + return HashBlake2b } } // erasureInfo - carries erasure coding related information, block // distribution and checksums. type erasureInfo struct { - Algorithm string `json:"algorithm"` + Algorithm HashAlgo `json:"algorithm"` DataBlocks int `json:"data"` ParityBlocks int `json:"parity"` BlockSize int64 `json:"blockSize"` diff --git a/cmd/xl-v1-object.go b/cmd/xl-v1-object.go index 31bb2bfa6..ec4c6ddd4 100644 --- a/cmd/xl-v1-object.go +++ b/cmd/xl-v1-object.go @@ -266,7 +266,7 @@ func (xl xlObjects) GetObject(bucket, object string, startOffset int64, length i // Get the checksums of the current part. checkSums := make([]string, len(onlineDisks)) - var ckSumAlgo string + var ckSumAlgo HashAlgo for index, disk := range onlineDisks { // Disk is not found skip the checksum. if disk == nil { diff --git a/cmd/xl-v1-utils.go b/cmd/xl-v1-utils.go index 95f4cdb1d..43b79cc2b 100644 --- a/cmd/xl-v1-utils.go +++ b/cmd/xl-v1-utils.go @@ -161,7 +161,7 @@ func parseXLErasureInfo(xlMetaBuf []byte) erasureInfo { } erasure.Distribution = distribution - erasure.Algorithm = erasureResult.Get("algorithm").String() + erasure.Algorithm = HashAlgo(erasureResult.Get("algorithm").String()) erasure.DataBlocks = int(erasureResult.Get("data").Int()) erasure.ParityBlocks = int(erasureResult.Get("parity").Int()) erasure.BlockSize = erasureResult.Get("blockSize").Int() @@ -172,7 +172,7 @@ func parseXLErasureInfo(xlMetaBuf []byte) erasureInfo { for i, checkSumResult := range checkSumsResult { checkSum := checkSumInfo{} checkSum.Name = checkSumResult.Get("name").String() - checkSum.Algorithm = checkSumResult.Get("algorithm").String() + checkSum.Algorithm = HashAlgo(checkSumResult.Get("algorithm").String()) checkSum.Hash = checkSumResult.Get("hash").String() checkSums[i] = checkSum } diff --git a/cmd/xl-v1-utils_test.go b/cmd/xl-v1-utils_test.go index 4cf1e6298..b5c4c8855 100644 --- a/cmd/xl-v1-utils_test.go +++ b/cmd/xl-v1-utils_test.go @@ -157,7 +157,7 @@ func newTestXLMetaV1() xlMetaV1 { return xlMeta } -func (m *xlMetaV1) AddTestObjectCheckSum(checkSumNum int, name string, hash string, algo string) { +func (m *xlMetaV1) AddTestObjectCheckSum(checkSumNum int, name string, hash string, algo HashAlgo) { checkSum := checkSumInfo{ Name: name, Algorithm: algo, From 59b3e0b79b715130732a8412228e9d7a85021a36 Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Tue, 16 May 2017 14:34:56 -0700 Subject: [PATCH 29/80] auth/rpc: Add RWMutex instead of Mutex for granular locking. (#4352) Refer https://github.com/minio/minio/issues/4345 --- cmd/auth-rpc-client.go | 82 +++++++++++++++++++++++------------------- 1 file changed, 46 insertions(+), 36 deletions(-) diff --git a/cmd/auth-rpc-client.go b/cmd/auth-rpc-client.go index 9b3c80fd1..f9e667662 100644 --- a/cmd/auth-rpc-client.go +++ b/cmd/auth-rpc-client.go @@ -52,10 +52,10 @@ type authConfig struct { // AuthRPCClient is a authenticated RPC client which does authentication before doing Call(). type AuthRPCClient struct { - sync.Mutex // Mutex to lock this object. - rpcClient *RPCClient // Reconnectable RPC client to make any RPC call. - config authConfig // Authentication configuration information. - authToken string // Authentication token. + sync.RWMutex // Mutex to lock this object. + rpcClient *RPCClient // Reconnectable RPC client to make any RPC call. + config authConfig // Authentication configuration information. + authToken string // Authentication token. } // newAuthRPCClient - returns a JWT based authenticated (go) rpc client, which does automatic reconnect. @@ -78,33 +78,43 @@ func newAuthRPCClient(config authConfig) *AuthRPCClient { } } -// Login - a jwt based authentication is performed with rpc server. +// Login a JWT based authentication is performed with rpc server. func (authClient *AuthRPCClient) Login() (err error) { + // Login should be attempted one at a time. + // + // The reason for large region lock here is + // to avoid two simultaneous login attempts + // racing over each other. + // + // #1 Login() gets the lock proceeds to login. + // #2 Login() waits for the unlock to happen + // after login in #1. + // #1 Successfully completes login saves the + // newly acquired token. + // #2 Successfully gets the lock and proceeds, + // but since we have acquired the token + // already the call quickly returns. authClient.Lock() defer authClient.Unlock() - // Return if already logged in. - if authClient.authToken != "" { - return nil + // Attempt to login if not logged in already. + if authClient.authToken == "" { + // Login to authenticate and acquire a new auth token. + var ( + loginMethod = authClient.config.serviceName + loginMethodName + loginArgs = LoginRPCArgs{ + Username: authClient.config.accessKey, + Password: authClient.config.secretKey, + Version: Version, + RequestTime: UTCNow(), + } + loginReply = LoginRPCReply{} + ) + if err = authClient.rpcClient.Call(loginMethod, &loginArgs, &loginReply); err != nil { + return err + } + authClient.authToken = loginReply.AuthToken } - - // Call login. - args := LoginRPCArgs{ - Username: authClient.config.accessKey, - Password: authClient.config.secretKey, - Version: Version, - RequestTime: UTCNow(), - } - - reply := LoginRPCReply{} - serviceMethod := authClient.config.serviceName + loginMethodName - if err = authClient.rpcClient.Call(serviceMethod, &args, &reply); err != nil { - return err - } - - // Logged in successfully. - authClient.authToken = reply.AuthToken - return nil } @@ -112,17 +122,17 @@ func (authClient *AuthRPCClient) Login() (err error) { func (authClient *AuthRPCClient) call(serviceMethod string, args interface { SetAuthToken(authToken string) }, 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) - authClient.Unlock() + if err = authClient.Login(); err != nil { + return err + } // On successful login, execute RPC call. - // Do RPC call. - err = authClient.rpcClient.Call(serviceMethod, args, reply) - } - return err + authClient.RLock() + // Set token before the rpc call. + args.SetAuthToken(authClient.authToken) + authClient.RUnlock() + + // Do an RPC call. + return authClient.rpcClient.Call(serviceMethod, args, reply) } // Call executes RPC call till success or globalAuthRPCRetryThreshold on ErrShutdown. From 542f7ae42c74f216ed0dc69dae8679de111d8ed0 Mon Sep 17 00:00:00 2001 From: Anis Elleuch Date: Wed, 17 May 2017 06:13:29 +0200 Subject: [PATCH 30/80] gateway: Reject endpoint pointing to local gateway (#4310) Show an error when the user enters an endpoint url pointing to the gateway server itself. --- cmd/endpoint.go | 6 +-- cmd/gateway-main.go | 19 +++++-- cmd/net.go | 123 ++++++++++++++++++++++++++++++++++++++++++-- cmd/net_test.go | 75 +++++++++++++++++++++++++++ 4 files changed, 212 insertions(+), 11 deletions(-) diff --git a/cmd/endpoint.go b/cmd/endpoint.go index 7b21c0cbc..30af75657 100644 --- a/cmd/endpoint.go +++ b/cmd/endpoint.go @@ -127,14 +127,10 @@ func NewEndpoint(arg string) (Endpoint, error) { return Endpoint{}, fmt.Errorf("empty or root path is not supported in URL endpoint") } - // Get IPv4 address of the host. - hostIPs, err := getHostIP4(host) + isLocal, err = isLocalHost(host) if err != nil { return Endpoint{}, err } - - // If intersection of two IP sets is not empty, then the host is local host. - isLocal = !localIP4.Intersection(hostIPs).IsEmpty() } else { u = &url.URL{Path: path.Clean(arg)} isLocal = true diff --git a/cmd/gateway-main.go b/cmd/gateway-main.go index 8b72e768f..9ca9d94e1 100644 --- a/cmd/gateway-main.go +++ b/cmd/gateway-main.go @@ -185,11 +185,24 @@ func gatewayMain(ctx *cli.Context) { } // First argument is selected backend type. - backendType := ctx.Args().First() + backendType := ctx.Args().Get(0) + // Second argument is the endpoint address (optional) + endpointAddr := ctx.Args().Get(1) + // Third argument is the address flag + serverAddr := ctx.String("address") + + if endpointAddr != "" { + // Reject the endpoint if it points to the gateway handler itself. + sameTarget, err := sameLocalAddrs(endpointAddr, serverAddr) + fatalIf(err, "Unable to compare server and endpoint addresses.") + if sameTarget { + fatalIf(errors.New("endpoint points to the local gateway"), "Endpoint url is not allowed") + } + } // Second argument is endpoint. If no endpoint is specified then the // gateway implementation should use a default setting. - endPoint, secure, err := parseGatewayEndpoint(ctx.Args().Get(1)) + endPoint, secure, err := parseGatewayEndpoint(endpointAddr) fatalIf(err, "Unable to parse endpoint") // Create certs path for SSL configuration. @@ -223,7 +236,7 @@ func gatewayMain(ctx *cli.Context) { setAuthHandler, } - apiServer := NewServerMux(ctx.String("address"), registerHandlers(router, handlerFns...)) + apiServer := NewServerMux(serverAddr, registerHandlers(router, handlerFns...)) _, _, globalIsSSL, err = getSSLConfig() fatalIf(err, "Invalid SSL key file") diff --git a/cmd/net.go b/cmd/net.go index 9f8d6021c..ba62c0d77 100644 --- a/cmd/net.go +++ b/cmd/net.go @@ -17,11 +17,14 @@ package cmd import ( + "errors" "fmt" "net" + "net/url" "os" "sort" "strconv" + "strings" "syscall" "github.com/minio/minio-go/pkg/set" @@ -186,6 +189,121 @@ func checkPortAvailability(port string) (err error) { return nil } +// extractHostPort - extracts host/port from many address formats +// such as, ":9000", "localhost:9000", "http://localhost:9000/" +func extractHostPort(hostAddr string) (string, string, error) { + var addr, scheme string + + if hostAddr == "" { + return "", "", errors.New("unable to process empty address") + } + + // Parse address to extract host and scheme field + u, err := url.Parse(hostAddr) + if err != nil { + // Ignore scheme not present error + if !strings.Contains(err.Error(), "missing protocol scheme") { + return "", "", err + } + } else { + addr = u.Host + scheme = u.Scheme + } + + // Use the given parameter again if url.Parse() + // didn't return any useful result. + if addr == "" { + addr = hostAddr + scheme = "http" + } + + // At this point, addr can be one of the following form: + // ":9000" + // "localhost:9000" + // "localhost" <- in this case, we check for scheme + + host, port, err := net.SplitHostPort(addr) + if err != nil { + if !strings.Contains(err.Error(), "missing port in address") { + return "", "", err + } + + host = addr + + switch scheme { + case "https": + port = "443" + case "http": + port = "80" + default: + return "", "", errors.New("unable to guess port from scheme") + } + } + + return host, port, nil +} + +// isLocalHost - checks if the given parameter +// correspond to one of the local IP of the +// current machine +func isLocalHost(host string) (bool, error) { + hostIPs, err := getHostIP4(host) + if err != nil { + return false, err + } + + // If intersection of two IP sets is not empty, then the host is local host. + isLocal := !localIP4.Intersection(hostIPs).IsEmpty() + return isLocal, nil +} + +// sameLocalAddrs - returns true if two addresses, even with different +// formats, point to the same machine, e.g: +// ':9000' and 'http://localhost:9000/' will return true +func sameLocalAddrs(addr1, addr2 string) (bool, error) { + + // Extract host & port from given parameters + host1, port1, err := extractHostPort(addr1) + if err != nil { + return false, err + } + host2, port2, err := extractHostPort(addr2) + if err != nil { + return false, err + } + + var addr1Local, addr2Local bool + + if host1 == "" { + // If empty host means it is localhost + addr1Local = true + } else { + // Host not empty, check if it is local + if addr1Local, err = isLocalHost(host1); err != nil { + return false, err + } + } + + if host2 == "" { + // If empty host means it is localhost + addr2Local = true + } else { + // Host not empty, check if it is local + if addr2Local, err = isLocalHost(host2); err != nil { + return false, err + } + } + + // If both of addresses point to the same machine, check if + // have the same port + if addr1Local && addr2Local { + if port1 == port2 { + return true, nil + } + } + return false, nil +} + // CheckLocalServerAddr - checks if serverAddr is valid and local host. func CheckLocalServerAddr(serverAddr string) error { host, port, err := net.SplitHostPort(serverAddr) @@ -202,12 +320,11 @@ func CheckLocalServerAddr(serverAddr string) error { } if host != "" { - hostIPs, err := getHostIP4(host) + isLocalHost, err := isLocalHost(host) if err != nil { return err } - - if localIP4.Intersection(hostIPs).IsEmpty() { + if !isLocalHost { return fmt.Errorf("host in server address should be this server") } } diff --git a/cmd/net_test.go b/cmd/net_test.go index e95e81b9d..9f2e9c9a0 100644 --- a/cmd/net_test.go +++ b/cmd/net_test.go @@ -17,6 +17,7 @@ package cmd import ( + "errors" "fmt" "net" "reflect" @@ -240,3 +241,77 @@ func TestCheckLocalServerAddr(t *testing.T) { } } } + +func TestExtractHostPort(t *testing.T) { + testCases := []struct { + addr string + host string + port string + expectedErr error + }{ + {"", "", "", errors.New("unable to process empty address")}, + {"localhost", "localhost", "80", nil}, + {"localhost:9000", "localhost", "9000", nil}, + {"http://:9000/", "", "9000", nil}, + {"http://8.8.8.8:9000/", "8.8.8.8", "9000", nil}, + {"https://facebook.com:9000/", "facebook.com", "9000", nil}, + } + + for i, testCase := range testCases { + host, port, err := extractHostPort(testCase.addr) + if testCase.expectedErr == nil { + if err != nil { + t.Fatalf("Test %d: should succeed but failed with err: %v", i+1, err) + } + if host != testCase.host { + t.Fatalf("Test %d: expected: %v, found: %v", i+1, testCase.host, host) + } + if port != testCase.port { + t.Fatalf("Test %d: expected: %v, found: %v", i+1, testCase.port, port) + } + + } + if testCase.expectedErr != nil { + if err == nil { + t.Fatalf("Test %d:, should fail but succeeded.", i+1) + } + if testCase.expectedErr.Error() != err.Error() { + t.Fatalf("Test %d: failed with different error, expected: '%v', found:'%v'.", i+1, testCase.expectedErr, err) + } + } + } +} + +func TestSameLocalAddrs(t *testing.T) { + testCases := []struct { + addr1 string + addr2 string + sameAddr bool + expectedErr error + }{ + {"", "", false, errors.New("unable to process empty address")}, + {":9000", ":9000", true, nil}, + {"localhost:9000", ":9000", true, nil}, + {"localhost:9000", "http://localhost:9000", true, nil}, + {"8.8.8.8:9000", "http://localhost:9000", false, nil}, + } + + for i, testCase := range testCases { + sameAddr, err := sameLocalAddrs(testCase.addr1, testCase.addr2) + if testCase.expectedErr != nil && err == nil { + t.Fatalf("Test %d: should fail but succeeded", i+1) + } + if testCase.expectedErr == nil && err != nil { + t.Fatalf("Test %d: should succeed but failed with %v", i+1, err) + } + if err == nil { + if sameAddr != testCase.sameAddr { + t.Fatalf("Test %d: expected: %v, found: %v", i+1, testCase.sameAddr, sameAddr) + } + } else { + if err.Error() != testCase.expectedErr.Error() { + t.Fatalf("Test %d: failed with different error, expected: '%v', found:'%v'.", i+1, testCase.expectedErr, err) + } + } + } +} From 1886d94e95825ad10410a413a685f6eb7557b776 Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Wed, 17 May 2017 11:57:52 -0700 Subject: [PATCH 31/80] server/mux: Use constants provided by Go http (#4360) --- cmd/server-mux.go | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/cmd/server-mux.go b/cmd/server-mux.go index 2e3128526..c7ca9f163 100644 --- a/cmd/server-mux.go +++ b/cmd/server-mux.go @@ -42,19 +42,22 @@ const ( maxHTTPVerbLen = 7 ) +// HTTP2 PRI method. +var httpMethodPRI = "PRI" + var defaultHTTP2Methods = []string{ - "PRI", + httpMethodPRI, } var defaultHTTP1Methods = []string{ - "OPTIONS", - "GET", - "HEAD", - "POST", - "PUT", - "DELETE", - "TRACE", - "CONNECT", + http.MethodOptions, + http.MethodGet, + http.MethodHead, + http.MethodPost, + http.MethodPut, + http.MethodDelete, + http.MethodTrace, + http.MethodConnect, } // ConnMux - Peeks into the incoming connection for relevant From a767ad321ad166fdfbb2040ee2d8aedf4e04476c Mon Sep 17 00:00:00 2001 From: Rushan Date: Thu, 18 May 2017 05:33:44 +0530 Subject: [PATCH 32/80] Browser: Fix Safari Blob download issue (#4357) --- browser/app/js/actions.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/browser/app/js/actions.js b/browser/app/js/actions.js index 7147ec0fe..69ac8419b 100644 --- a/browser/app/js/actions.js +++ b/browser/app/js/actions.js @@ -432,6 +432,8 @@ export const setLoginError = () => { export const downloadSelected = (url, req, xhr) => { return (dispatch) => { + var anchor = document.createElement('a') + document.body.appendChild(anchor); xhr.open('POST', url, true) xhr.responseType = 'blob' @@ -439,10 +441,20 @@ export const downloadSelected = (url, req, xhr) => { if (this.status == 200) { dispatch(checkedObjectsReset()) var blob = new Blob([this.response], { - type: 'application/zip' + type: 'octet/stream' }) var blobUrl = window.URL.createObjectURL(blob); - window.location = blobUrl + var separator = req.prefix.length > 1 ? '-' : '' + + anchor.href = blobUrl + anchor.download = req.bucketName+separator+req.prefix.slice(0, -1)+'.zip'; + + + + + anchor.click() + window.URL.revokeObjectURL(blobUrl) + anchor.remove() } }; xhr.send(JSON.stringify(req)); From 9d98bf1c0fb83ccfcf368dec52b2d19b9195b8d4 Mon Sep 17 00:00:00 2001 From: luomeiqin Date: Fri, 19 May 2017 22:30:00 +0800 Subject: [PATCH 33/80] Bucket names can contain hyphen (#4324) --- cmd/web-handlers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/web-handlers.go b/cmd/web-handlers.go index 17a97a727..2bed40359 100644 --- a/cmd/web-handlers.go +++ b/cmd/web-handlers.go @@ -897,7 +897,7 @@ func toJSONError(err error, params ...string) (jerr *json2.Error) { case "InvalidBucketName": if len(params) > 0 { jerr = &json2.Error{ - Message: fmt.Sprintf("Bucket Name %s is invalid. Lowercase letters, period, numerals are the only allowed characters and should be minimum 3 characters in length.", params[0]), + Message: fmt.Sprintf("Bucket Name %s is invalid. Lowercase letters, period, hyphen, numerals are the only allowed characters and should be minimum 3 characters in length.", params[0]), } } // Bucket not found custom error message. From 348aa6566cbfc7e256b3244f5b159850eeefa9f5 Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Mon, 22 May 2017 07:05:39 -0700 Subject: [PATCH 34/80] Add nsswitch.conf to our docker image (#4379) This will cause the alpine images to resolve DNS using /etc/hosts along with the normal DNS resolver. --- Dockerfile | 3 +++ Dockerfile.aarch64 | 3 +++ Dockerfile.armhf | 3 +++ 3 files changed, 9 insertions(+) diff --git a/Dockerfile b/Dockerfile index af9261a9c..a3150ba1c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,7 @@ FROM alpine:3.5 +MAINTAINER Minio Inc + ENV GOPATH /go ENV PATH $PATH:$GOPATH/bin ENV CGO_ENABLED 0 @@ -9,6 +11,7 @@ WORKDIR /go/src/github.com/minio/ RUN \ apk add --no-cache ca-certificates && \ apk add --no-cache --virtual .build-deps git go musl-dev && \ + echo 'hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4' >> /etc/nsswitch.conf && \ 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)" && \ diff --git a/Dockerfile.aarch64 b/Dockerfile.aarch64 index 2334a73cf..d558d2f69 100644 --- a/Dockerfile.aarch64 +++ b/Dockerfile.aarch64 @@ -1,5 +1,7 @@ FROM resin/aarch64-alpine:3.5 +MAINTAINER Minio Inc + ENV GOPATH /go ENV PATH $PATH:$GOPATH/bin ENV CGO_ENABLED 0 @@ -9,6 +11,7 @@ WORKDIR /go/src/github.com/minio/ RUN \ apk add --no-cache ca-certificates && \ apk add --no-cache --virtual .build-deps git go musl-dev && \ + echo 'hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4' >> /etc/nsswitch.conf && \ 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)" && \ diff --git a/Dockerfile.armhf b/Dockerfile.armhf index 33e8b65ed..dbbe55ee3 100644 --- a/Dockerfile.armhf +++ b/Dockerfile.armhf @@ -1,5 +1,7 @@ FROM resin/armhf-alpine:3.5 +MAINTAINER Minio Inc + ENV GOPATH /go ENV PATH $PATH:$GOPATH/bin ENV CGO_ENABLED 0 @@ -9,6 +11,7 @@ WORKDIR /go/src/github.com/minio/ RUN \ apk add --no-cache ca-certificates && \ apk add --no-cache --virtual .build-deps git go musl-dev && \ + echo 'hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4' >> /etc/nsswitch.conf && \ 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)" && \ From 9136734c023f201d6c6e6c475f2943ed9fc5656b Mon Sep 17 00:00:00 2001 From: morph027 Date: Mon, 22 May 2017 20:30:30 +0200 Subject: [PATCH 35/80] added secrets to distributed swarm minio (#4374) --- docs/orchestration/docker-swarm/README.md | 16 +++- .../docker-swarm/docker-compose-secrets.yaml | 93 +++++++++++++++++++ 2 files changed, 105 insertions(+), 4 deletions(-) create mode 100644 docs/orchestration/docker-swarm/docker-compose-secrets.yaml diff --git a/docs/orchestration/docker-swarm/README.md b/docs/orchestration/docker-swarm/README.md index 21da1c3f5..142a3bdef 100644 --- a/docs/orchestration/docker-swarm/README.md +++ b/docs/orchestration/docker-swarm/README.md @@ -24,13 +24,21 @@ 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](https://github.com/minio/minio/blob/master/docs/orchestration/docker-swarm/docker-compose.yaml?raw=true) on your Swarm master. Then execute the command +## 3. Create Docker secrets for Minio ```shell -docker stack deploy --compose-file=docker-compose.yaml minio_stack +echo "AKIAIOSFODNN7EXAMPLE" | docker secret create access_key - +echo "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" | docker secret create secret_key - ``` + +## 4. Deploy distributed Minio services + +Download the [Docker Compose file](https://github.com/minio/minio/blob/master/docs/orchestration/docker-swarm/docker-compose-secrets.yaml?raw=true) on your Swarm master. Then execute the command + +```shell +docker stack deploy --compose-file=docker-compose-secrets.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] diff --git a/docs/orchestration/docker-swarm/docker-compose-secrets.yaml b/docs/orchestration/docker-swarm/docker-compose-secrets.yaml new file mode 100644 index 000000000..bccca3a4d --- /dev/null +++ b/docs/orchestration/docker-swarm/docker-compose-secrets.yaml @@ -0,0 +1,93 @@ +version: '3.1' + +services: + minio1: + image: minio/minio:RELEASE.2017-05-05T01-14-51Z + volumes: + - minio1-data:/export + ports: + - "9001:9000" + networks: + - minio_distributed + deploy: + restart_policy: + delay: 10s + max_attempts: 10 + window: 60s + command: server http://minio1/export http://minio2/export http://minio3/export http://minio4/export + secrets: + - secret_key + - access_key + + minio2: + image: minio/minio:RELEASE.2017-05-05T01-14-51Z + volumes: + - minio2-data:/export + ports: + - "9002:9000" + networks: + - minio_distributed + deploy: + restart_policy: + delay: 10s + max_attempts: 10 + window: 60s + command: server http://minio1/export http://minio2/export http://minio3/export http://minio4/export + secrets: + - secret_key + - access_key + + minio3: + image: minio/minio:RELEASE.2017-05-05T01-14-51Z + volumes: + - minio3-data:/export + ports: + - "9003:9000" + networks: + - minio_distributed + deploy: + restart_policy: + delay: 10s + max_attempts: 10 + window: 60s + command: server http://minio1/export http://minio2/export http://minio3/export http://minio4/export + secrets: + - secret_key + - access_key + + minio4: + image: minio/minio:RELEASE.2017-05-05T01-14-51Z + volumes: + - minio4-data:/export + ports: + - "9004:9000" + networks: + - minio_distributed + deploy: + restart_policy: + delay: 10s + max_attempts: 10 + window: 60s + command: server http://minio1/export http://minio2/export http://minio3/export http://minio4/export + secrets: + - secret_key + - access_key + +volumes: + minio1-data: + + minio2-data: + + minio3-data: + + minio4-data: + +networks: + minio_distributed: + driver: overlay + +secrets: + secret_key: + external: true + access_key: + external: true From 28c26a9e597f9fb3d33f531c3b8007679bd60cec Mon Sep 17 00:00:00 2001 From: Krishna Srinivas Date: Mon, 22 May 2017 15:42:00 -0700 Subject: [PATCH 36/80] Generate random ETag if client does not provide MD5 for PutObjectPart (#4385) fixes #4289 fixes #4290 --- cmd/gateway-azure.go | 65 ++++++++++++++++++++++++++++++++++++----- cmd/gateway-handlers.go | 5 ++++ 2 files changed, 63 insertions(+), 7 deletions(-) diff --git a/cmd/gateway-azure.go b/cmd/gateway-azure.go index 2c5e87949..0c5be7b11 100644 --- a/cmd/gateway-azure.go +++ b/cmd/gateway-azure.go @@ -17,6 +17,7 @@ package cmd import ( + "crypto/md5" "encoding/base64" "encoding/hex" "fmt" @@ -313,24 +314,47 @@ func canonicalMetadata(metadata map[string]string) (canonical map[string]string) // 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 + var md5sumWriter hash.Hash + + var writers []io.Writer + + md5sum := metadata["etag"] + delete(metadata, "etag") + teeReader := data + if sha256sum != "" { sha256Writer = sha256.New() - teeReader = io.TeeReader(data, sha256Writer) + writers = append(writers, sha256Writer) } - delete(metadata, "etag") + if md5sum != "" { + md5sumWriter = md5.New() + writers = append(writers, md5sumWriter) + } + + if len(writers) > 0 { + teeReader = io.TeeReader(data, io.MultiWriter(writers...)) + } err = a.client.CreateBlockBlobFromReader(bucket, object, uint64(size), teeReader, canonicalMetadata(metadata)) if err != nil { return objInfo, azureToObjectError(traceError(err), bucket, object) } + if md5sum != "" { + newMD5sum := hex.EncodeToString(md5sumWriter.Sum(nil)) + if newMD5sum != md5sum { + a.client.DeleteBlob(bucket, object, nil) + return ObjectInfo{}, azureToObjectError(traceError(BadDigest{md5sum, newMD5sum})) + } + } + if sha256sum != "" { newSHA256sum := hex.EncodeToString(sha256Writer.Sum(nil)) if newSHA256sum != sha256sum { a.client.DeleteBlob(bucket, object, nil) - return ObjectInfo{}, traceError(SHA256Mismatch{}) + return ObjectInfo{}, azureToObjectError(traceError(SHA256Mismatch{})) } } @@ -426,27 +450,54 @@ func (a *azureObjects) PutObjectPart(bucket, object, uploadID string, partID int return info, traceError(InvalidUploadID{}) } var sha256Writer hash.Hash + var md5sumWriter hash.Hash + var etag string + + var writers []io.Writer + if sha256sum != "" { sha256Writer = sha256.New() + writers = append(writers, sha256Writer) } - teeReader := io.TeeReader(data, sha256Writer) + if md5Hex != "" { + md5sumWriter = md5.New() + writers = append(writers, md5sumWriter) + etag = md5Hex + } else { + // Generate random ETag. + etag = getMD5Hash([]byte(mustGetUUID())) + } - id := azureGetBlockID(partID, md5Hex) + teeReader := data + + if len(writers) > 0 { + teeReader = io.TeeReader(data, io.MultiWriter(writers...)) + } + + id := azureGetBlockID(partID, etag) err = a.client.PutBlockWithLength(bucket, object, id, uint64(size), teeReader, nil) if err != nil { return info, azureToObjectError(traceError(err), bucket, object) } + if md5Hex != "" { + newMD5sum := hex.EncodeToString(md5sumWriter.Sum(nil)) + if newMD5sum != md5Hex { + a.client.DeleteBlob(bucket, object, nil) + return PartInfo{}, azureToObjectError(traceError(BadDigest{md5Hex, newMD5sum})) + } + } + if sha256sum != "" { newSHA256sum := hex.EncodeToString(sha256Writer.Sum(nil)) if newSHA256sum != sha256sum { - return PartInfo{}, traceError(SHA256Mismatch{}) + return PartInfo{}, azureToObjectError(traceError(SHA256Mismatch{})) } } info.PartNumber = partID - info.ETag = md5Hex + info.ETag = etag info.LastModified = UTCNow() info.Size = size return info, nil diff --git a/cmd/gateway-handlers.go b/cmd/gateway-handlers.go index 456b4446d..553de071b 100644 --- a/cmd/gateway-handlers.go +++ b/cmd/gateway-handlers.go @@ -282,6 +282,11 @@ func (api gatewayAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Re return } + if err != nil { + writeErrorResponse(w, toAPIErrorCode(err), r.URL) + return + } + w.Header().Set("ETag", "\""+objInfo.ETag+"\"") writeSuccessResponseHeadersOnly(w) } From 3c5db69ffd6bf6637aca6b357ab5889568f943b6 Mon Sep 17 00:00:00 2001 From: Krishnan Parthasarathi Date: Wed, 24 May 2017 00:37:39 +0530 Subject: [PATCH 37/80] Treat 0.0.0.0 as local address in --address flag (#4386) --- cmd/net.go | 5 ++++- cmd/net_test.go | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/cmd/net.go b/cmd/net.go index ba62c0d77..d846773cc 100644 --- a/cmd/net.go +++ b/cmd/net.go @@ -319,7 +319,10 @@ func CheckLocalServerAddr(serverAddr string) error { return fmt.Errorf("port number must be between 1 to 65535") } - if host != "" { + // 0.0.0.0 is a wildcard address and refers to local network + // addresses. I.e, 0.0.0.0:9000 like ":9000" refers to port + // 9000 on localhost. + if host != "" && host != net.IPv4zero.String() { isLocalHost, err := isLocalHost(host) if err != nil { return err diff --git a/cmd/net_test.go b/cmd/net_test.go index 9f2e9c9a0..f020c27f4 100644 --- a/cmd/net_test.go +++ b/cmd/net_test.go @@ -221,6 +221,7 @@ func TestCheckLocalServerAddr(t *testing.T) { }{ {":54321", nil}, {"localhost:54321", nil}, + {"0.0.0.0:9000", nil}, {"", fmt.Errorf("missing port in address")}, {"localhost", fmt.Errorf("missing port in address localhost")}, {"example.org:54321", fmt.Errorf("host in server address should be this server")}, From 9b3dd446076582e513979679e75b3e8a68d97935 Mon Sep 17 00:00:00 2001 From: poornas Date: Tue, 23 May 2017 13:57:27 -0700 Subject: [PATCH 38/80] Add dotnet library to minio startup message (#4410) --- cmd/server-startup-msg.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/cmd/server-startup-msg.go b/cmd/server-startup-msg.go index bd23bd63e..a56c2ac98 100644 --- a/cmd/server-startup-msg.go +++ b/cmd/server-startup-msg.go @@ -27,11 +27,12 @@ import ( // Documentation links, these are part of message printing code. const ( - mcQuickStartGuide = "https://docs.minio.io/docs/minio-client-quickstart-guide" - goQuickStartGuide = "https://docs.minio.io/docs/golang-client-quickstart-guide" - jsQuickStartGuide = "https://docs.minio.io/docs/javascript-client-quickstart-guide" - javaQuickStartGuide = "https://docs.minio.io/docs/java-client-quickstart-guide" - pyQuickStartGuide = "https://docs.minio.io/docs/python-client-quickstart-guide" + mcQuickStartGuide = "https://docs.minio.io/docs/minio-client-quickstart-guide" + goQuickStartGuide = "https://docs.minio.io/docs/golang-client-quickstart-guide" + jsQuickStartGuide = "https://docs.minio.io/docs/javascript-client-quickstart-guide" + javaQuickStartGuide = "https://docs.minio.io/docs/java-client-quickstart-guide" + pyQuickStartGuide = "https://docs.minio.io/docs/python-client-quickstart-guide" + dotnetQuickStartGuide = "https://docs.minio.io/docs/dotnet-client-quickstart-guide" ) // generates format string depending on the string length and padding. @@ -130,6 +131,7 @@ func printObjectAPIMsg() { log.Println(colorBlue(" Java: ") + fmt.Sprintf(getFormatStr(len(javaQuickStartGuide), 6), javaQuickStartGuide)) log.Println(colorBlue(" Python: ") + fmt.Sprintf(getFormatStr(len(pyQuickStartGuide), 4), pyQuickStartGuide)) log.Println(colorBlue(" JavaScript: ") + jsQuickStartGuide) + log.Println(colorBlue(" .NET: ") + fmt.Sprintf(getFormatStr(len(dotnetQuickStartGuide), 6), dotnetQuickStartGuide)) } // Get formatted disk/storage info message. From 99ca8a2928c1236efbd89026dc448b87e8f54a7a Mon Sep 17 00:00:00 2001 From: samkevich Date: Wed, 24 May 2017 06:07:52 +0300 Subject: [PATCH 39/80] fix InvalidAccessKeyId error according to amazon documentation (#4404) http://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html --- cmd/api-errors.go | 2 +- cmd/bucket-handlers_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/api-errors.go b/cmd/api-errors.go index bde76e784..d8ef0b124 100644 --- a/cmd/api-errors.go +++ b/cmd/api-errors.go @@ -230,7 +230,7 @@ var errorCodeResponse = map[APIErrorCode]APIError{ HTTPStatusCode: http.StatusInternalServerError, }, ErrInvalidAccessKeyID: { - Code: "InvalidAccessKeyID", + Code: "InvalidAccessKeyId", Description: "The access key ID you provided does not exist in our records.", HTTPStatusCode: http.StatusForbidden, }, diff --git a/cmd/bucket-handlers_test.go b/cmd/bucket-handlers_test.go index 212c066eb..75a7fe44a 100644 --- a/cmd/bucket-handlers_test.go +++ b/cmd/bucket-handlers_test.go @@ -68,7 +68,7 @@ func testGetBucketLocationHandler(obj ObjectLayer, instanceType, bucketName stri locationResponse: []byte(""), errorResponse: APIErrorResponse{ Resource: "/" + bucketName + "/", - Code: "InvalidAccessKeyID", + Code: "InvalidAccessKeyId", Message: "The access key ID you provided does not exist in our records.", }, shouldPass: false, From b78f6fbcc5240f6ced0dbe1533a32ce76b5cb5b6 Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Wed, 24 May 2017 21:09:23 -0700 Subject: [PATCH 40/80] Do not send envVars in ServerInfo() (#4422) Sending envVars along with access and secret exposes the entire minio server's sensitive information. This will be an unexpected situation for all users. If at all we need to look for things like if credentials are set through env, we should only have access to only this information not the entire set of system envs. --- Makefile | 2 +- browser/app/js/components/Browse.js | 2 +- browser/app/js/components/SettingsModal.js | 21 +++++---------------- cmd/globals.go | 22 ++++++++++++++++++++++ cmd/web-handlers.go | 14 +++++++------- cmd/web-handlers_test.go | 4 ++++ 6 files changed, 40 insertions(+), 25 deletions(-) diff --git a/Makefile b/Makefile index e4998ae69..4db4954ba 100644 --- a/Makefile +++ b/Makefile @@ -124,7 +124,7 @@ test: build @echo "Done." coverage: build - @echo -n "Running all coverage for minio: " + @echo "Running all coverage for minio: " @./buildscripts/go-coverage.sh @echo "Done." diff --git a/browser/app/js/components/Browse.js b/browser/app/js/components/Browse.js index b4cbb769e..5b0accd29 100644 --- a/browser/app/js/components/Browse.js +++ b/browser/app/js/components/Browse.js @@ -68,7 +68,7 @@ export default class Browse extends React.Component { memory: res.MinioMemory, platform: res.MinioPlatform, runtime: res.MinioRuntime, - envVars: res.MinioEnvVars + info: res.MinioGlobalInfo }) dispatch(actions.setServerInfo(serverInfo)) }) diff --git a/browser/app/js/components/SettingsModal.js b/browser/app/js/components/SettingsModal.js index 9d3263a46..189a2b9f8 100644 --- a/browser/app/js/components/SettingsModal.js +++ b/browser/app/js/components/SettingsModal.js @@ -34,23 +34,12 @@ class SettingsModal extends React.Component { let accessKeyEnv = '' let secretKeyEnv = '' - // Check environment variables first. They may or may not have been - // loaded already; they load in Browse#componentDidMount. - if (serverInfo.envVars) { - serverInfo.envVars.forEach(envVar => { - let keyVal = envVar.split('=') - if (keyVal[0] == 'MINIO_ACCESS_KEY') { - accessKeyEnv = keyVal[1] - } else if (keyVal[0] == 'MINIO_SECRET_KEY') { - secretKeyEnv = keyVal[1] - } - }) - } - if (accessKeyEnv != '' || secretKeyEnv != '') { + // Check environment variables first. + if (serverInfo.info.isEnvCreds) { dispatch(actions.setSettings({ - accessKey: accessKeyEnv, - secretKey: secretKeyEnv, - keysReadOnly: true + accessKey: 'xxxxxxxxx', + secretKey: 'xxxxxxxxx', + keysReadOnly: true })) } else { web.GetAuth() diff --git a/cmd/globals.go b/cmd/globals.go index a9444dd88..b57457b9c 100644 --- a/cmd/globals.go +++ b/cmd/globals.go @@ -64,6 +64,7 @@ var ( // This flag is set to 'true' by default globalIsBrowserEnabled = true + // This flag is set to 'true' when MINIO_BROWSER env is set. globalIsEnvBrowser = false @@ -72,6 +73,7 @@ var ( // This flag is set to 'true' wen MINIO_REGION env is set. globalIsEnvRegion = false + // This flag is set to 'us-east-1' by default globalServerRegion = globalMinioDefaultRegion @@ -128,3 +130,23 @@ var ( colorBold = color.New(color.Bold).SprintFunc() colorBlue = color.New(color.FgBlue).SprintfFunc() ) + +// Returns minio global information, as a key value map. +// returned list of global values is not an exhaustive +// list. Feel free to add new relevant fields. +func getGlobalInfo() (globalInfo map[string]interface{}) { + globalInfo = map[string]interface{}{ + "isDistXL": globalIsDistXL, + "isXL": globalIsXL, + "isBrowserEnabled": globalIsBrowserEnabled, + "isEnvBrowser": globalIsEnvBrowser, + "isEnvCreds": globalIsEnvCreds, + "isEnvRegion": globalIsEnvRegion, + "isSSL": globalIsSSL, + "serverRegion": globalServerRegion, + "serverUserAgent": globalServerUserAgent, + // Add more relevant global settings here. + } + + return globalInfo +} diff --git a/cmd/web-handlers.go b/cmd/web-handlers.go index 2bed40359..653e64872 100644 --- a/cmd/web-handlers.go +++ b/cmd/web-handlers.go @@ -50,12 +50,12 @@ type WebGenericRep struct { // ServerInfoRep - server info reply. type ServerInfoRep struct { - MinioVersion string - MinioMemory string - MinioPlatform string - MinioRuntime string - MinioEnvVars []string - UIVersion string `json:"uiVersion"` + MinioVersion string + MinioMemory string + MinioPlatform string + MinioRuntime string + MinioGlobalInfo map[string]interface{} + UIVersion string `json:"uiVersion"` } // ServerInfo - get server info. @@ -80,8 +80,8 @@ func (web *webAPIHandlers) ServerInfo(r *http.Request, args *WebGenericArgs, rep runtime.GOARCH) goruntime := fmt.Sprintf("Version: %s | CPUs: %s", runtime.Version(), strconv.Itoa(runtime.NumCPU())) - reply.MinioEnvVars = os.Environ() reply.MinioVersion = Version + reply.MinioGlobalInfo = getGlobalInfo() reply.MinioMemory = mem reply.MinioPlatform = platform reply.MinioRuntime = goruntime diff --git a/cmd/web-handlers_test.go b/cmd/web-handlers_test.go index 4120aa4c0..64488268b 100644 --- a/cmd/web-handlers_test.go +++ b/cmd/web-handlers_test.go @@ -236,6 +236,10 @@ func testServerInfoWebHandler(obj ObjectLayer, instanceType string, t TestErrHan if serverInfoReply.MinioVersion != Version { t.Fatalf("Cannot get minio version from server info handler") } + globalInfo := getGlobalInfo() + if !reflect.DeepEqual(serverInfoReply.MinioGlobalInfo, globalInfo) { + t.Fatalf("Global info did not match got %#v, expected %#v", serverInfoReply.MinioGlobalInfo, globalInfo) + } } // Wrapper for calling MakeBucket Web Handler From 072fcf3ba624f4cdabd2a46425c038316fce634c Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Thu, 25 May 2017 09:22:43 -0700 Subject: [PATCH 41/80] fs: Make sure to validate bucket first in PutObject() (#4427) Currently even when bucket doesn't exist we wrongly return success, when an object is a directory prefix with '/' as suffix and is of size 0. This PR fixes this behavior. --- cmd/fs-v1.go | 16 +++++++++------- cmd/fs-v1_test.go | 25 +++++++++++++++++++++++-- 2 files changed, 32 insertions(+), 9 deletions(-) diff --git a/cmd/fs-v1.go b/cmd/fs-v1.go index 4f862285a..b33a700fb 100644 --- a/cmd/fs-v1.go +++ b/cmd/fs-v1.go @@ -605,9 +605,15 @@ func (fs fsObjects) parentDirIsObject(bucket, parent string) bool { // for future object operations. func (fs fsObjects) PutObject(bucket string, object string, size int64, data io.Reader, metadata map[string]string, sha256sum string) (objInfo ObjectInfo, retErr error) { var err error - // This is a special case with size as '0' and object ends with - // a slash separator, we treat it like a valid operation and - // return success. + + // Validate if bucket name is valid and exists. + if _, err = fs.statBucketDir(bucket); err != nil { + return ObjectInfo{}, toObjectErr(err, bucket) + } + + // This is a special case with size as '0' and object ends + // with 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. if fs.parentDirIsObject(bucket, path.Dir(object)) { @@ -625,10 +631,6 @@ func (fs fsObjects) PutObject(bucket string, object string, size int64, data io. return ObjectInfo{}, toObjectErr(traceError(errFileAccessDenied), bucket, object) } - if _, err = fs.statBucketDir(bucket); err != nil { - return ObjectInfo{}, toObjectErr(err, bucket) - } - // No metadata is set, allocate a new one. if metadata == nil { metadata = make(map[string]string) diff --git a/cmd/fs-v1_test.go b/cmd/fs-v1_test.go index a425ba3e4..71962e99c 100644 --- a/cmd/fs-v1_test.go +++ b/cmd/fs-v1_test.go @@ -523,6 +523,7 @@ func TestFSGetBucketInfo(t *testing.T) { } } +// Tests FS backend put object behavior. func TestFSPutObject(t *testing.T) { // Prepare for tests disk := filepath.Join(globalTestTmpDir, "minio-"+nextSuffix()) @@ -535,14 +536,34 @@ func TestFSPutObject(t *testing.T) { if err := obj.MakeBucket(bucketName); err != nil { t.Fatal(err) } + sha256sum := "" - _, err := obj.PutObject(bucketName, objectName, int64(len("abcd")), bytes.NewReader([]byte("abcd")), nil, sha256sum) + + // With a regular object. + _, err := obj.PutObject(bucketName+"non-existent", objectName, int64(len("abcd")), bytes.NewReader([]byte("abcd")), nil, sha256sum) + if err == nil { + t.Fatal("Unexpected should fail here, bucket doesn't exist") + } + if _, ok := errorCause(err).(BucketNotFound); !ok { + t.Fatalf("Expected error type BucketNotFound, got %#v", err) + } + + // With a directory object. + _, err = obj.PutObject(bucketName+"non-existent", objectName+"/", int64(0), bytes.NewReader([]byte("")), nil, sha256sum) + if err == nil { + t.Fatal("Unexpected should fail here, bucket doesn't exist") + } + if _, ok := errorCause(err).(BucketNotFound); !ok { + t.Fatalf("Expected error type BucketNotFound, got %#v", err) + } + + _, err = obj.PutObject(bucketName, objectName, int64(len("abcd")), bytes.NewReader([]byte("abcd")), nil, sha256sum) if err != nil { t.Fatal(err) } _, err = obj.PutObject(bucketName, objectName+"/1", int64(len("abcd")), bytes.NewReader([]byte("abcd")), nil, sha256sum) if err == nil { - t.Fatal("Unexpected should fail here, backned corruption occurred") + t.Fatal("Unexpected should fail here, backend corruption occurred") } if nerr, ok := errorCause(err).(PrefixAccessDenied); !ok { t.Fatalf("Expected PrefixAccessDenied, got %#v", err) From e01b2fc06d6eaf5ffdfba37bd82ea23fa3b24cd1 Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Tue, 30 May 2017 11:02:31 -0700 Subject: [PATCH 42/80] Disable network share test, appveyor bug. (#4446) --- pkg/x/os/stat_windows_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/x/os/stat_windows_test.go b/pkg/x/os/stat_windows_test.go index 2e063096f..27ede7257 100644 --- a/pkg/x/os/stat_windows_test.go +++ b/pkg/x/os/stat_windows_test.go @@ -71,6 +71,9 @@ func sameFile(fi1, fi2 os1.FileInfo) bool { } func TestNetworkSymbolicLink(t *testing.T) { + // Skipping this test for now - appveyor builds are failing. + t.Skip() + dir, err := ioutil.TempDir("", "TestNetworkSymbolicLink") if err != nil { t.Fatal(err) From bac5303b1006061557e32948b154374802bf62f9 Mon Sep 17 00:00:00 2001 From: Cesar Alvernaz Date: Tue, 30 May 2017 19:03:25 +0100 Subject: [PATCH 43/80] Some minor fixes (#4441) Word terminations --- docs/orchestration/dcos/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/orchestration/dcos/README.md b/docs/orchestration/dcos/README.md index aae1f63fe..703f0dc7a 100644 --- a/docs/orchestration/dcos/README.md +++ b/docs/orchestration/dcos/README.md @@ -14,7 +14,7 @@ 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`. +Visit the DC/OS admin page, and click on `Universe` on the left menu bar. Then click on the `Packages` tab and search for Minio. Once you see the package, click the `Install Package` 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. From 0bba3cc8e3e8d2f2adb3d3ef7cf946dcc6078750 Mon Sep 17 00:00:00 2001 From: Krishna Srinivas Date: Tue, 30 May 2017 20:05:41 -0700 Subject: [PATCH 44/80] gateway-azure: Convert S3 metadata to azure metadata (#4384) fixes #4292 --- cmd/gateway-azure.go | 70 ++++++++++++++++++++++++++++----------- cmd/gateway-azure_test.go | 27 +++++++++++---- 2 files changed, 72 insertions(+), 25 deletions(-) diff --git a/cmd/gateway-azure.go b/cmd/gateway-azure.go index 0c5be7b11..39e7f4065 100644 --- a/cmd/gateway-azure.go +++ b/cmd/gateway-azure.go @@ -37,6 +37,36 @@ import ( const globalAzureAPIVersion = "2016-05-31" +// 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. +// Also replaces X-Amz-Meta prefix with X-Ms-Meta as Azure expects user +// defined metadata to have X-Ms-Meta prefix. +func s3ToAzureHeaders(headers map[string]string) (newHeaders map[string]string) { + newHeaders = make(map[string]string) + for k, v := range headers { + k = http.CanonicalHeaderKey(k) + if strings.HasPrefix(k, "X-Amz-Meta") { + k = strings.Replace(k, "X-Amz-Meta", "X-Ms-Meta", -1) + } + newHeaders[k] = v + } + return newHeaders +} + +// Prefix user metadata with "X-Amz-Meta-". +// client.GetBlobMetadata() already strips "X-Ms-Meta-" +func azureToS3Metadata(meta map[string]string) (newMeta map[string]string) { + newMeta = make(map[string]string) + + for k, v := range meta { + k = "X-Amz-Meta-" + k + newMeta[k] = v + } + return newMeta +} + // To store metadata during NewMultipartUpload which will be used after // CompleteMultipartUpload to call SetBlobMetadata. type azureMultipartMetaInfo struct { @@ -275,6 +305,13 @@ func (a *azureObjects) GetObject(bucket, object string, startOffset int64, lengt // GetObjectInfo - reads blob metadata properties and replies back ObjectInfo, // uses zure equivalent GetBlobProperties. func (a *azureObjects) GetObjectInfo(bucket, object string) (objInfo ObjectInfo, err error) { + blobMeta, err := a.client.GetBlobMetadata(bucket, object) + if err != nil { + return objInfo, azureToObjectError(traceError(err), bucket, object) + } + + meta := azureToS3Metadata(blobMeta) + prop, err := a.client.GetBlobProperties(bucket, object) if err != nil { return objInfo, azureToObjectError(traceError(err), bucket, object) @@ -283,31 +320,22 @@ func (a *azureObjects) GetObjectInfo(bucket, object string) (objInfo ObjectInfo, if err != nil { return objInfo, traceError(err) } + + if prop.ContentEncoding != "" { + meta["Content-Encoding"] = prop.ContentEncoding + } + meta["Content-Type"] = prop.ContentType + objInfo = ObjectInfo{ Bucket: bucket, - UserDefined: make(map[string]string), + UserDefined: meta, ETag: 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 + return objInfo, nil } // PutObject - Create a new blob with the incoming data, @@ -337,7 +365,7 @@ func (a *azureObjects) PutObject(bucket, object string, size int64, data io.Read teeReader = io.TeeReader(data, io.MultiWriter(writers...)) } - err = a.client.CreateBlockBlobFromReader(bucket, object, uint64(size), teeReader, canonicalMetadata(metadata)) + err = a.client.CreateBlockBlobFromReader(bucket, object, uint64(size), teeReader, s3ToAzureHeaders(metadata)) if err != nil { return objInfo, azureToObjectError(traceError(err), bucket, object) } @@ -410,7 +438,7 @@ func (a *azureObjects) NewMultipartUpload(bucket, object string, metadata map[st // Store an empty map as a placeholder else ListObjectParts/PutObjectPart will not work properly. metadata = make(map[string]string) } else { - metadata = canonicalMetadata(metadata) + metadata = s3ToAzureHeaders(metadata) } a.metaInfo.set(uploadID, metadata) return uploadID, nil @@ -587,6 +615,10 @@ func (a *azureObjects) CompleteMultipartUpload(bucket, object, uploadID string, if err != nil { return objInfo, azureToObjectError(traceError(err), bucket, object) } + err = a.client.SetBlobMetadata(bucket, object, nil, meta) + if err != nil { + return objInfo, azureToObjectError(traceError(err), bucket, object) + } } a.metaInfo.del(uploadID) return a.GetObjectInfo(bucket, object) diff --git a/cmd/gateway-azure_test.go b/cmd/gateway-azure_test.go index b7d99debe..7e5639b7f 100644 --- a/cmd/gateway-azure_test.go +++ b/cmd/gateway-azure_test.go @@ -25,18 +25,33 @@ import ( ) // Test canonical metadata. -func TestCanonicalMetadata(t *testing.T) { - metadata := map[string]string{ +func TestS3ToAzureHeaders(t *testing.T) { + headers := map[string]string{ "accept-encoding": "gzip", "content-encoding": "gzip", } - expectedCanonicalM := map[string]string{ + expectedHeaders := 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) + actualHeaders := s3ToAzureHeaders(headers) + if !reflect.DeepEqual(actualHeaders, expectedHeaders) { + t.Fatalf("Test failed, expected %#v, got %#v", expectedHeaders, actualHeaders) + } +} + +func TestAzureToS3Metadata(t *testing.T) { + // Just one testcase. Adding more test cases does not add value to the testcase + // as azureToS3Metadata() just adds a prefix. + metadata := map[string]string{ + "First-Name": "myname", + } + expectedMeta := map[string]string{ + "X-Amz-Meta-First-Name": "myname", + } + actualMeta := azureToS3Metadata(metadata) + if !reflect.DeepEqual(actualMeta, expectedMeta) { + t.Fatalf("Test failed, expected %#v, got %#v", expectedMeta, actualMeta) } } From 458f22f37c090121029795784f06e0f5013ced3c Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Wed, 31 May 2017 00:11:06 -0700 Subject: [PATCH 45/80] log: Fix printing of signature error request headers. (#4444) The following commit f44f2e341c931222a8f1e3f1e9911dda80bb2dc0 fix was incomplete and we still had presigned URLs printing in query strings in wrong fashion. This PR fixes this properly. Avoid double encoding percent encoded strings such as `s3%!!(MISSING)A(MISSING)` Print properly as json encoded. `s3%3AObjectCreated%3A%2A` --- cmd/auth-handler.go | 4 ++-- cmd/gateway-handlers.go | 26 +++++++++++++------------- cmd/object-handlers.go | 12 ++++++------ cmd/utils.go | 26 ++++++++++++++++---------- cmd/utils_test.go | 21 ++++++++++++++------- 5 files changed, 51 insertions(+), 38 deletions(-) diff --git a/cmd/auth-handler.go b/cmd/auth-handler.go index cdfbfe5a8..85fce51a6 100644 --- a/cmd/auth-handler.go +++ b/cmd/auth-handler.go @@ -113,13 +113,13 @@ func checkRequestAuthType(r *http.Request, bucket, policyAction, region string) // Signature V2 validation. s3Error := isReqAuthenticatedV2(r) if s3Error != ErrNone { - errorIf(errSignatureMismatch, dumpRequest(r)) + errorIf(errSignatureMismatch, "%s", dumpRequest(r)) } return s3Error case authTypeSigned, authTypePresigned: s3Error := isReqAuthenticated(r, region) if s3Error != ErrNone { - errorIf(errSignatureMismatch, dumpRequest(r)) + errorIf(errSignatureMismatch, "%s", dumpRequest(r)) } return s3Error } diff --git a/cmd/gateway-handlers.go b/cmd/gateway-handlers.go index 553de071b..4ce311f72 100644 --- a/cmd/gateway-handlers.go +++ b/cmd/gateway-handlers.go @@ -54,14 +54,14 @@ func (api gatewayAPIHandlers) GetObjectHandler(w http.ResponseWriter, r *http.Re // Signature V2 validation. s3Error := isReqAuthenticatedV2(r) if s3Error != ErrNone { - errorIf(errSignatureMismatch, dumpRequest(r)) + errorIf(errSignatureMismatch, "%s", dumpRequest(r)) writeErrorResponse(w, s3Error, r.URL) return } case authTypeSigned, authTypePresigned: s3Error := isReqAuthenticated(r, serverConfig.GetRegion()) if s3Error != ErrNone { - errorIf(errSignatureMismatch, dumpRequest(r)) + errorIf(errSignatureMismatch, "%s", dumpRequest(r)) writeErrorResponse(w, s3Error, r.URL) return } @@ -252,7 +252,7 @@ func (api gatewayAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Re // Initialize stream signature verifier. reader, s3Error := newSignV4ChunkedReader(r) if s3Error != ErrNone { - errorIf(errSignatureMismatch, dumpRequest(r)) + errorIf(errSignatureMismatch, "%s", dumpRequest(r)) writeErrorResponse(w, s3Error, r.URL) return } @@ -260,14 +260,14 @@ func (api gatewayAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Re case authTypeSignedV2, authTypePresignedV2: s3Error := isReqAuthenticatedV2(r) if s3Error != ErrNone { - errorIf(errSignatureMismatch, dumpRequest(r)) + errorIf(errSignatureMismatch, "%s", dumpRequest(r)) writeErrorResponse(w, s3Error, r.URL) return } objInfo, err = objectAPI.PutObject(bucket, object, size, r.Body, metadata, sha256sum) case authTypePresigned, authTypeSigned: if s3Error := reqSignatureV4Verify(r, serverConfig.GetRegion()); s3Error != ErrNone { - errorIf(errSignatureMismatch, dumpRequest(r)) + errorIf(errSignatureMismatch, "%s", dumpRequest(r)) writeErrorResponse(w, s3Error, r.URL) return } @@ -313,14 +313,14 @@ func (api gatewayAPIHandlers) HeadObjectHandler(w http.ResponseWriter, r *http.R // Signature V2 validation. s3Error := isReqAuthenticatedV2(r) if s3Error != ErrNone { - errorIf(errSignatureMismatch, dumpRequest(r)) + errorIf(errSignatureMismatch, "%s", dumpRequest(r)) writeErrorResponse(w, s3Error, r.URL) return } case authTypeSigned, authTypePresigned: s3Error := isReqAuthenticated(r, serverConfig.GetRegion()) if s3Error != ErrNone { - errorIf(errSignatureMismatch, dumpRequest(r)) + errorIf(errSignatureMismatch, "%s", dumpRequest(r)) writeErrorResponse(w, s3Error, r.URL) return } @@ -727,14 +727,14 @@ func (api gatewayAPIHandlers) ListObjectsV1Handler(w http.ResponseWriter, r *htt // Signature V2 validation. s3Error := isReqAuthenticatedV2(r) if s3Error != ErrNone { - errorIf(errSignatureMismatch, dumpRequest(r)) + errorIf(errSignatureMismatch, "%s", dumpRequest(r)) writeErrorResponse(w, s3Error, r.URL) return } case authTypeSigned, authTypePresigned: s3Error := isReqAuthenticated(r, serverConfig.GetRegion()) if s3Error != ErrNone { - errorIf(errSignatureMismatch, dumpRequest(r)) + errorIf(errSignatureMismatch, "%s", dumpRequest(r)) writeErrorResponse(w, s3Error, r.URL) return } @@ -797,14 +797,14 @@ func (api gatewayAPIHandlers) HeadBucketHandler(w http.ResponseWriter, r *http.R // Signature V2 validation. s3Error := isReqAuthenticatedV2(r) if s3Error != ErrNone { - errorIf(errSignatureMismatch, dumpRequest(r)) + errorIf(errSignatureMismatch, "%s", dumpRequest(r)) writeErrorResponse(w, s3Error, r.URL) return } case authTypeSigned, authTypePresigned: s3Error := isReqAuthenticated(r, serverConfig.GetRegion()) if s3Error != ErrNone { - errorIf(errSignatureMismatch, dumpRequest(r)) + errorIf(errSignatureMismatch, "%s", dumpRequest(r)) writeErrorResponse(w, s3Error, r.URL) return } @@ -849,7 +849,7 @@ func (api gatewayAPIHandlers) GetBucketLocationHandler(w http.ResponseWriter, r // Signature V2 validation. s3Error := isReqAuthenticatedV2(r) if s3Error != ErrNone { - errorIf(errSignatureMismatch, dumpRequest(r)) + errorIf(errSignatureMismatch, "%s", dumpRequest(r)) writeErrorResponse(w, s3Error, r.URL) return } @@ -860,7 +860,7 @@ func (api gatewayAPIHandlers) GetBucketLocationHandler(w http.ResponseWriter, r s3Error = isReqAuthenticated(r, serverConfig.GetRegion()) } if s3Error != ErrNone { - errorIf(errSignatureMismatch, dumpRequest(r)) + errorIf(errSignatureMismatch, "%s", dumpRequest(r)) writeErrorResponse(w, s3Error, r.URL) return } diff --git a/cmd/object-handlers.go b/cmd/object-handlers.go index bc432a097..3a1352fdf 100644 --- a/cmd/object-handlers.go +++ b/cmd/object-handlers.go @@ -507,7 +507,7 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req // Initialize stream signature verifier. reader, s3Error := newSignV4ChunkedReader(r) if s3Error != ErrNone { - errorIf(errSignatureMismatch, dumpRequest(r)) + errorIf(errSignatureMismatch, "%s", dumpRequest(r)) writeErrorResponse(w, s3Error, r.URL) return } @@ -515,14 +515,14 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req case authTypeSignedV2, authTypePresignedV2: s3Error := isReqAuthenticatedV2(r) if s3Error != ErrNone { - errorIf(errSignatureMismatch, dumpRequest(r)) + errorIf(errSignatureMismatch, "%s", dumpRequest(r)) writeErrorResponse(w, s3Error, r.URL) return } objInfo, err = objectAPI.PutObject(bucket, object, size, r.Body, metadata, sha256sum) case authTypePresigned, authTypeSigned: if s3Error := reqSignatureV4Verify(r, serverConfig.GetRegion()); s3Error != ErrNone { - errorIf(errSignatureMismatch, dumpRequest(r)) + errorIf(errSignatureMismatch, "%s", dumpRequest(r)) writeErrorResponse(w, s3Error, r.URL) return } @@ -788,7 +788,7 @@ func (api objectAPIHandlers) PutObjectPartHandler(w http.ResponseWriter, r *http // Initialize stream signature verifier. reader, s3Error := newSignV4ChunkedReader(r) if s3Error != ErrNone { - errorIf(errSignatureMismatch, dumpRequest(r)) + errorIf(errSignatureMismatch, "%s", dumpRequest(r)) writeErrorResponse(w, s3Error, r.URL) return } @@ -796,14 +796,14 @@ func (api objectAPIHandlers) PutObjectPartHandler(w http.ResponseWriter, r *http case authTypeSignedV2, authTypePresignedV2: s3Error := isReqAuthenticatedV2(r) if s3Error != ErrNone { - errorIf(errSignatureMismatch, dumpRequest(r)) + errorIf(errSignatureMismatch, "%s", dumpRequest(r)) writeErrorResponse(w, s3Error, r.URL) return } partInfo, err = objectAPI.PutObjectPart(bucket, object, uploadID, partID, size, r.Body, incomingMD5, sha256sum) case authTypePresigned, authTypeSigned: if s3Error := reqSignatureV4Verify(r, serverConfig.GetRegion()); s3Error != ErrNone { - errorIf(errSignatureMismatch, dumpRequest(r)) + errorIf(errSignatureMismatch, "%s", dumpRequest(r)) writeErrorResponse(w, s3Error, r.URL) return } diff --git a/cmd/utils.go b/cmd/utils.go index 37e2ee292..398de1398 100644 --- a/cmd/utils.go +++ b/cmd/utils.go @@ -17,6 +17,7 @@ package cmd import ( + "bytes" "encoding/base64" "encoding/json" "encoding/xml" @@ -163,20 +164,25 @@ var globalProfiler interface { func dumpRequest(r *http.Request) string { header := cloneHeader(r.Header) header.Set("Host", r.Host) + // Replace all '%' to '%%' so that printer format parser + // to ignore URL encoded values. + rawURI := strings.Replace(r.RequestURI, "%", "%%", -1) req := struct { - Method string `json:"method"` - Path string `json:"path"` - Query string `json:"query"` - Header http.Header `json:"header"` - }{r.Method, getURLEncodedName(r.URL.Path), r.URL.RawQuery, header} - jsonBytes, err := json.Marshal(&req) - if err != nil { + Method string `json:"method"` + RequestURI string `json:"reqURI"` + Header http.Header `json:"header"` + }{r.Method, rawURI, header} + + var buffer bytes.Buffer + enc := json.NewEncoder(&buffer) + enc.SetEscapeHTML(false) + if err := enc.Encode(&req); err != nil { // Upon error just return Go-syntax representation of the value return fmt.Sprintf("%#v", req) } - // Replace all '%' to '%%' so that printer format parser - // to ignore URL encoded values. - return strings.Replace(string(jsonBytes), "%", "%%", -1) + + // Formatted string. + return strings.TrimSpace(string(buffer.Bytes())) } // isFile - returns whether given path is a file or not. diff --git a/cmd/utils_test.go b/cmd/utils_test.go index b67f8ea7e..f364ce4f7 100644 --- a/cmd/utils_test.go +++ b/cmd/utils_test.go @@ -248,17 +248,17 @@ func TestCheckURL(t *testing.T) { // Testing dumping request function. func TestDumpRequest(t *testing.T) { - req, err := http.NewRequest("GET", "http://localhost:9000?prefix=Hello%2AWorld%2A", nil) + req, err := http.NewRequest("GET", "http://localhost:9000?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=USWUXHGYZQYFYFFIT3RE%2F20170529%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20170529T190139Z&X-Amz-Expires=600&X-Amz-Signature=19b58080999df54b446fc97304eb8dda60d3df1812ae97f3e8783351bfd9781d&X-Amz-SignedHeaders=host&prefix=Hello%2AWorld%2A", nil) if err != nil { t.Fatal(err) } + req.RequestURI = "/?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=USWUXHGYZQYFYFFIT3RE%2F20170529%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20170529T190139Z&X-Amz-Expires=600&X-Amz-Signature=19b58080999df54b446fc97304eb8dda60d3df1812ae97f3e8783351bfd9781d&X-Amz-SignedHeaders=host&prefix=Hello%2AWorld%2A" req.Header.Set("content-md5", "====test") jsonReq := dumpRequest(req) type jsonResult struct { - Method string `json:"method"` - Path string `json:"path"` - Query string `json:"query"` - Header http.Header `json:"header"` + Method string `json:"method"` + RequestURI string `json:"reqURI"` + Header http.Header `json:"header"` } jsonReq = strings.Replace(jsonReq, "%%", "%", -1) res := jsonResult{} @@ -274,8 +274,15 @@ func TestDumpRequest(t *testing.T) { // Look for expected query values expectedQuery := url.Values{} expectedQuery.Set("prefix", "Hello*World*") - if !reflect.DeepEqual(res.Query, expectedQuery.Encode()) { - t.Fatalf("Expected %#v, got %#v", expectedQuery, res.Query) + expectedQuery.Set("X-Amz-Algorithm", "AWS4-HMAC-SHA256") + expectedQuery.Set("X-Amz-Credential", "USWUXHGYZQYFYFFIT3RE/20170529/us-east-1/s3/aws4_request") + expectedQuery.Set("X-Amz-Date", "20170529T190139Z") + expectedQuery.Set("X-Amz-Expires", "600") + expectedQuery.Set("X-Amz-SignedHeaders", "host") + expectedQuery.Set("X-Amz-Signature", "19b58080999df54b446fc97304eb8dda60d3df1812ae97f3e8783351bfd9781d") + expectedRequestURI := "/?" + expectedQuery.Encode() + if !reflect.DeepEqual(res.RequestURI, expectedRequestURI) { + t.Fatalf("Expected %#v, got %#v", expectedRequestURI, res.RequestURI) } // Look for expected header. From 975972d57e4d8e71b5188eeacb81c3df4ae3ba39 Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Wed, 31 May 2017 09:21:02 -0700 Subject: [PATCH 46/80] server: Redirection should use globalMinioPort with host without port. (#4445) Currently redirection doesn't work in following scenarios - server started with port ":80" and TLS is configured client requested insecure request on port "80" gets redirected to port 443 and fails. --- cmd/server-mux.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/cmd/server-mux.go b/cmd/server-mux.go index c7ca9f163..4977c9d5a 100644 --- a/cmd/server-mux.go +++ b/cmd/server-mux.go @@ -449,12 +449,20 @@ func (m *ServerMux) ListenAndServe(certFile, keyFile string) (err error) { // All http requests start to be processed by httpHandler httpHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if tlsEnabled && r.TLS == nil { + // It is expected that r.Host might not have port + // for standard ports such as "80" and "443". + host, port, _ := net.SplitHostPort(r.Host) + if port == "" { + host = net.JoinHostPort(r.Host, globalMinioPort) + } else { + host = r.Host + } // TLS is enabled but Request is not TLS configured u := url.URL{ Scheme: httpsScheme, Opaque: r.URL.Opaque, User: r.URL.User, - Host: r.Host, + Host: host, Path: r.URL.Path, RawQuery: r.URL.RawQuery, Fragment: r.URL.Fragment, From 28352f3f5dd094e0fa4a2fa21a331d24a76cf15f Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Wed, 31 May 2017 09:21:28 -0700 Subject: [PATCH 47/80] log: Startup banner should strip standard ports. (#4443) APIEndpoints list should strip off standard ports to avoid confusion with clients. --- cmd/server-startup-msg.go | 36 ++++++++++++++++++++++++++++++++-- cmd/server-startup-msg_test.go | 30 +++++++++++++++++++++++++--- 2 files changed, 61 insertions(+), 5 deletions(-) diff --git a/cmd/server-startup-msg.go b/cmd/server-startup-msg.go index a56c2ac98..9f4549eee 100644 --- a/cmd/server-startup-msg.go +++ b/cmd/server-startup-msg.go @@ -19,6 +19,7 @@ package cmd import ( "crypto/x509" "fmt" + "net/url" "runtime" "strings" @@ -44,12 +45,14 @@ func getFormatStr(strLen int, padding int) string { // Prints the formatted startup message. func printStartupMessage(apiEndPoints []string) { + strippedAPIEndpoints := stripStandardPorts(apiEndPoints) + // Prints credential, region and browser access. - printServerCommonMsg(apiEndPoints) + printServerCommonMsg(strippedAPIEndpoints) // Prints `mc` cli configuration message chooses // first endpoint as default. - printCLIAccessMsg(apiEndPoints[0]) + printCLIAccessMsg(strippedAPIEndpoints[0]) // Prints documentation message. printObjectAPIMsg() @@ -67,6 +70,34 @@ func printStartupMessage(apiEndPoints []string) { } } +// strip api endpoints list with standard ports such as +// port "80" and "443" before displaying on the startup +// banner. Returns a new list of API endpoints. +func stripStandardPorts(apiEndpoints []string) (newAPIEndpoints []string) { + newAPIEndpoints = make([]string, len(apiEndpoints)) + // Check all API endpoints for standard ports and strip them. + for i, apiEndpoint := range apiEndpoints { + url, err := url.Parse(apiEndpoint) + if err != nil { + newAPIEndpoints[i] = apiEndpoint + continue + } + host, port := mustSplitHostPort(url.Host) + // For standard HTTP(s) ports such as "80" and "443" + // apiEndpoints should only be host without port. + switch { + case url.Scheme == "http" && port == "80": + fallthrough + case url.Scheme == "https" && port == "443": + url.Host = host + newAPIEndpoints[i] = url.String() + default: + newAPIEndpoints[i] = apiEndpoint + } + } + return newAPIEndpoints +} + // Prints common server startup message. Prints credential, region and browser access. func printServerCommonMsg(apiEndpoints []string) { // Get saved credentials. @@ -76,6 +107,7 @@ func printServerCommonMsg(apiEndpoints []string) { region := serverConfig.GetRegion() apiEndpointStr := strings.Join(apiEndpoints, " ") + // Colorize the message and print. log.Println(colorBlue("\nEndpoint: ") + colorBold(fmt.Sprintf(getFormatStr(len(apiEndpointStr), 1), apiEndpointStr))) log.Println(colorBlue("AccessKey: ") + colorBold(fmt.Sprintf("%s ", cred.AccessKey))) diff --git a/cmd/server-startup-msg_test.go b/cmd/server-startup-msg_test.go index ef39b9e18..2da77901c 100644 --- a/cmd/server-startup-msg_test.go +++ b/cmd/server-startup-msg_test.go @@ -20,6 +20,7 @@ import ( "crypto/x509" "crypto/x509/pkix" "fmt" + "reflect" "strings" "testing" "time" @@ -95,6 +96,29 @@ func TestCertificateNotExpired(t *testing.T) { } } +// Tests stripping standard ports from apiEndpoints. +func TestStripStandardPorts(t *testing.T) { + apiEndpoints := []string{"http://127.0.0.1:9000", "http://127.0.0.2:80", "https://127.0.0.3:443"} + expectedAPIEndpoints := []string{"http://127.0.0.1:9000", "http://127.0.0.2", "https://127.0.0.3"} + newAPIEndpoints := stripStandardPorts(apiEndpoints) + + if !reflect.DeepEqual(expectedAPIEndpoints, newAPIEndpoints) { + t.Fatalf("Expected %#v, got %#v", expectedAPIEndpoints, newAPIEndpoints) + } + + apiEndpoints = []string{"http://%%%%%:9000"} + newAPIEndpoints = stripStandardPorts(apiEndpoints) + if !reflect.DeepEqual(apiEndpoints, newAPIEndpoints) { + t.Fatalf("Expected %#v, got %#v", apiEndpoints, newAPIEndpoints) + } + + apiEndpoints = []string{"http://127.0.0.1:443", "https://127.0.0.1:80"} + newAPIEndpoints = stripStandardPorts(apiEndpoints) + if !reflect.DeepEqual(apiEndpoints, newAPIEndpoints) { + t.Fatalf("Expected %#v, got %#v", apiEndpoints, newAPIEndpoints) + } +} + // Test printing server common message. func TestPrintServerCommonMessage(t *testing.T) { root, err := newTestConfig(globalMinioDefaultRegion) @@ -103,7 +127,7 @@ func TestPrintServerCommonMessage(t *testing.T) { } defer removeAll(root) - apiEndpoints := []string{"127.0.0.1:9000"} + apiEndpoints := []string{"http://127.0.0.1:9000"} printServerCommonMsg(apiEndpoints) } @@ -115,7 +139,7 @@ func TestPrintCLIAccessMsg(t *testing.T) { } defer removeAll(root) - apiEndpoints := []string{"127.0.0.1:9000"} + apiEndpoints := []string{"http://127.0.0.1:9000"} printCLIAccessMsg(apiEndpoints[0]) } @@ -127,6 +151,6 @@ func TestPrintStartupMessage(t *testing.T) { } defer removeAll(root) - apiEndpoints := []string{"127.0.0.1:9000"} + apiEndpoints := []string{"http://127.0.0.1:9000"} printStartupMessage(apiEndpoints) } From a0e02f43e1d02a6bac244cdae2839054be40d446 Mon Sep 17 00:00:00 2001 From: Aditya Manthramurthy Date: Wed, 31 May 2017 09:22:00 -0700 Subject: [PATCH 48/80] Fix and cleanup update message and improve related tests (#4361) Fixes #4232 --- cmd/server-main.go | 4 +- cmd/update-main.go | 13 +++--- cmd/update-notifier.go | 55 +++++++++++++++---------- cmd/update-notifier_test.go | 82 ++++++++++++++++++++++++++++--------- 4 files changed, 106 insertions(+), 48 deletions(-) diff --git a/cmd/server-main.go b/cmd/server-main.go index ac3efa6a7..0d44cedfa 100644 --- a/cmd/server-main.go +++ b/cmd/server-main.go @@ -81,8 +81,8 @@ EXAMPLES: 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) { - log.Println(colorizeUpdateMessage(downloadURL, older)) + if updateMsg := computeUpdateMessage(downloadURL, older); updateMsg != "" { + log.Println(updateMsg) } } } diff --git a/cmd/update-main.go b/cmd/update-main.go index b67db05e0..a8d902091 100644 --- a/cmd/update-main.go +++ b/cmd/update-main.go @@ -238,13 +238,16 @@ func getDownloadURL() (downloadURL string) { return minioReleaseURL + "minio" } -func getUpdateInfo(timeout time.Duration, mode string) (older time.Duration, downloadURL string, err error) { - currentReleaseTime, err := GetCurrentReleaseTime() +func getUpdateInfo(timeout time.Duration, mode string) (older time.Duration, + downloadURL string, err error) { + + var currentReleaseTime, latestReleaseTime time.Time + currentReleaseTime, err = GetCurrentReleaseTime() if err != nil { return older, downloadURL, err } - latestReleaseTime, err := getLatestReleaseTime(timeout, mode) + latestReleaseTime, err = getLatestReleaseTime(timeout, mode) if err != nil { return older, downloadURL, err } @@ -274,8 +277,8 @@ func mainUpdate(ctx *cli.Context) { os.Exit(-1) } - if older != time.Duration(0) { - log.Println(colorizeUpdateMessage(downloadURL, older)) + if updateMsg := computeUpdateMessage(downloadURL, older); updateMsg != "" { + log.Println(updateMsg) os.Exit(1) } diff --git a/cmd/update-notifier.go b/cmd/update-notifier.go index 3584f9ed9..16399492d 100644 --- a/cmd/update-notifier.go +++ b/cmd/update-notifier.go @@ -28,25 +28,39 @@ import ( "github.com/fatih/color" ) +// computeUpdateMessage - calculates the update message, only if a +// newer version is available. +func computeUpdateMessage(downloadURL string, older time.Duration) string { + if downloadURL == "" || older <= 0 { + return "" + } + + // Compute friendly duration string to indicate time + // difference between newer and current release. + t := time.Time{} + newerThan := humanize.RelTime(t, t.Add(older), "ago", "") + + // Return the nicely colored and formatted update message. + return colorizeUpdateMessage(downloadURL, newerThan) +} + // colorizeUpdateMessage - inspired from Yeoman project npm package https://github.com/yeoman/update-notifier -func colorizeUpdateMessage(updateString string, newerThan time.Duration) string { +func colorizeUpdateMessage(updateString string, newerThan string) string { // Initialize coloring. cyan := color.New(color.FgCyan, color.Bold).SprintFunc() yellow := color.New(color.FgYellow, color.Bold).SprintfFunc() - // Calculate length without color coding, due to ANSI color - // characters padded to actual string the final length is wrong - // than the original string length. - newerThanStr := humanize.Time(UTCNow().Add(newerThan)) + msgLine1Fmt := " You are running an older version of Minio released %s " + msgLine2Fmt := " Update: %s " - line1Str := fmt.Sprintf(" You are running an older version of Minio released %s ", newerThanStr) - line2Str := fmt.Sprintf(" Update: %s ", updateString) - line1Length := len(line1Str) - line2Length := len(line2Str) + // Calculate length *without* color coding: with ANSI terminal + // color characters, the result is incorrect. + line1Length := len(fmt.Sprintf(msgLine1Fmt, newerThan)) + line2Length := len(fmt.Sprintf(msgLine2Fmt, updateString)) // Populate lines with color coding. - line1InColor := fmt.Sprintf(" You are running an older version of Minio released %s ", yellow(newerThanStr)) - line2InColor := fmt.Sprintf(" Update: %s ", cyan(updateString)) + line1InColor := fmt.Sprintf(msgLine1Fmt, yellow(newerThan)) + line2InColor := fmt.Sprintf(msgLine2Fmt, cyan(updateString)) // calculate the rectangular box size. maxContentWidth := int(math.Max(float64(line1Length), float64(line2Length))) @@ -60,7 +74,7 @@ func colorizeUpdateMessage(updateString string, newerThan time.Duration) string // Box cannot be printed if terminal width is small than maxContentWidth if maxContentWidth > termWidth { - return "\n" + line1InColor + "\n" + line2InColor + "\n" + "\n" + return "\n" + line1InColor + "\n" + line2InColor + "\n\n" } topLeftChar := "┏" @@ -79,14 +93,11 @@ func colorizeUpdateMessage(updateString string, newerThan time.Duration) string vertBarChar = "|" } - message := "\n" - // Add top line - message += yellow(topLeftChar+strings.Repeat(horizBarChar, maxContentWidth)+topRightChar) + "\n" - // Add message lines - message += vertBarChar + line1InColor + strings.Repeat(" ", maxContentWidth-line1Length) + vertBarChar + "\n" - message += vertBarChar + line2InColor + strings.Repeat(" ", maxContentWidth-line2Length) + vertBarChar + "\n" - // Add bottom line - message += yellow(bottomLeftChar+strings.Repeat(horizBarChar, maxContentWidth)+bottomRightChar) + "\n" - - return message + lines := []string{ + yellow(topLeftChar + strings.Repeat(horizBarChar, maxContentWidth) + topRightChar), + vertBarChar + line1InColor + strings.Repeat(" ", maxContentWidth-line1Length) + vertBarChar, + vertBarChar + line2InColor + strings.Repeat(" ", maxContentWidth-line2Length) + vertBarChar, + yellow(bottomLeftChar + strings.Repeat(horizBarChar, maxContentWidth) + bottomRightChar), + } + return "\n" + strings.Join(lines, "\n") + "\n" } diff --git a/cmd/update-notifier_test.go b/cmd/update-notifier_test.go index 6ea79288f..f6506b8df 100644 --- a/cmd/update-notifier_test.go +++ b/cmd/update-notifier_test.go @@ -17,7 +17,7 @@ package cmd import ( - "runtime" + "fmt" "strings" "testing" "time" @@ -26,25 +26,69 @@ import ( ) // Tests update notifier string builder. -func TestUpdateNotifier(t *testing.T) { - plainMsg := "You are running an older version of Minio released " - colorMsg := plainMsg +func TestComputeUpdateMessage(t *testing.T) { + testCases := []struct { + older time.Duration + dlURL string + + expectedSubStr string + }{ + // Testcase index 0 + {72 * time.Hour, "my_download_url", "3 days ago"}, + {3 * time.Hour, "https://my_download_url_is_huge/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "3 hours ago"}, + {-72 * time.Hour, "another_update_url", ""}, + {0, "another_update_url", ""}, + {time.Hour, "", ""}, + {1 * time.Second, "my_download_url", "now"}, + {2 * time.Second, "my_download_url", "1 second ago"}, + {37 * time.Second, "my_download_url", "37 seconds ago"}, + {60 * time.Second, "my_download_url", "60 seconds ago"}, + {61 * time.Second, "my_download_url", "1 minute ago"}, + + // Testcase index 10 + {37 * time.Minute, "my_download_url", "37 minutes ago"}, + {1 * time.Hour, "my_download_url", "60 minutes ago"}, + {61 * time.Minute, "my_download_url", "1 hour ago"}, + {122 * time.Minute, "my_download_url", "2 hours ago"}, + {24 * time.Hour, "my_download_url", "24 hours ago"}, + {25 * time.Hour, "my_download_url", "1 day ago"}, + {49 * time.Hour, "my_download_url", "2 days ago"}, + {7 * 24 * time.Hour, "my_download_url", "7 days ago"}, + {8 * 24 * time.Hour, "my_download_url", "1 week ago"}, + {15 * 24 * time.Hour, "my_download_url", "2 weeks ago"}, + + // Testcase index 20 + {30 * 24 * time.Hour, "my_download_url", "4 weeks ago"}, + {31 * 24 * time.Hour, "my_download_url", "1 month ago"}, + {61 * 24 * time.Hour, "my_download_url", "2 months ago"}, + {360 * 24 * time.Hour, "my_download_url", "12 months ago"}, + {361 * 24 * time.Hour, "my_download_url", "1 year ago"}, + {2 * 365 * 24 * time.Hour, "my_download_url", "2 years ago"}, + } + + plainMsg := "You are running an older version of Minio released" yellow := color.New(color.FgYellow, color.Bold).SprintfFunc() - if runtime.GOOS == "windows" { - plainMsg += "3 days from now" - colorMsg += yellow("3 days from now") - } else { - plainMsg += "2 days from now" - colorMsg += yellow("2 days from now") - } + cyan := color.New(color.FgCyan, color.Bold).SprintFunc() - updateMsg := colorizeUpdateMessage(minioReleaseURL, time.Duration(72*time.Hour)) - - if !(strings.Contains(updateMsg, plainMsg) || strings.Contains(updateMsg, colorMsg)) { - t.Fatal("Duration string not found in colorized update message", updateMsg) - } - - if !strings.Contains(updateMsg, minioReleaseURL) { - t.Fatal("Update message not found in colorized update message", minioReleaseURL) + for i, testCase := range testCases { + output := computeUpdateMessage(testCase.dlURL, testCase.older) + line1 := fmt.Sprintf("%s %s", plainMsg, yellow(testCase.expectedSubStr)) + line2 := fmt.Sprintf("Update: %s", cyan(testCase.dlURL)) + // Uncomment below to see message appearance: + // fmt.Println(output) + switch { + case testCase.dlURL == "" && output != "": + t.Errorf("Testcase %d: no newer release available but got an update message: %s", i, output) + case output == "" && testCase.dlURL != "" && testCase.older > 0: + t.Errorf("Testcase %d: newer release is available but got empty update message!", i) + case output == "" && (testCase.dlURL == "" || testCase.older <= 0): + // Valid no update message case. No further + // validation needed. + continue + case !strings.Contains(output, line1): + t.Errorf("Testcase %d: output '%s' did not contain line 1: '%s'", i, output, line1) + case !strings.Contains(output, line2): + t.Errorf("Testcase %d: output '%s' did not contain line 2: '%s'", i, output, line2) + } } } From 0f0758aeced1a65ebb7d1070ea6c031d79484205 Mon Sep 17 00:00:00 2001 From: Frank Wessels Date: Wed, 31 May 2017 09:22:53 -0700 Subject: [PATCH 49/80] Load IO error count for posix atomically (#4448) * Load error count atomically in order to check for maximum allowed number of IO errors. * Remove unused (previously atomic) network IO error count --- cmd/posix.go | 26 +++++++++++++------------- cmd/storage-rpc-client.go | 1 - 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/cmd/posix.go b/cmd/posix.go index fb7b025c9..024e8aacd 100644 --- a/cmd/posix.go +++ b/cmd/posix.go @@ -251,7 +251,7 @@ func (s *posix) MakeVol(volume string) (err error) { } }() - if s.ioErrCount > maxAllowedIOError { + if atomic.LoadInt32(&s.ioErrCount) > maxAllowedIOError { return errFaultyDisk } @@ -285,7 +285,7 @@ func (s *posix) ListVols() (volsInfo []VolInfo, err error) { } }() - if s.ioErrCount > maxAllowedIOError { + if atomic.LoadInt32(&s.ioErrCount) > maxAllowedIOError { return nil, errFaultyDisk } @@ -349,7 +349,7 @@ func (s *posix) StatVol(volume string) (volInfo VolInfo, err error) { } }() - if s.ioErrCount > maxAllowedIOError { + if atomic.LoadInt32(&s.ioErrCount) > maxAllowedIOError { return VolInfo{}, errFaultyDisk } @@ -388,7 +388,7 @@ func (s *posix) DeleteVol(volume string) (err error) { } }() - if s.ioErrCount > maxAllowedIOError { + if atomic.LoadInt32(&s.ioErrCount) > maxAllowedIOError { return errFaultyDisk } @@ -422,7 +422,7 @@ func (s *posix) ListDir(volume, dirPath string) (entries []string, err error) { } }() - if s.ioErrCount > maxAllowedIOError { + if atomic.LoadInt32(&s.ioErrCount) > maxAllowedIOError { return nil, errFaultyDisk } @@ -459,7 +459,7 @@ func (s *posix) ReadAll(volume, path string) (buf []byte, err error) { } }() - if s.ioErrCount > maxAllowedIOError { + if atomic.LoadInt32(&s.ioErrCount) > maxAllowedIOError { return nil, errFaultyDisk } @@ -544,7 +544,7 @@ func (s *posix) ReadFileWithVerify(volume, path string, offset int64, buf []byte } }() - if s.ioErrCount > maxAllowedIOError { + if atomic.LoadInt32(&s.ioErrCount) > maxAllowedIOError { return 0, errFaultyDisk } @@ -664,7 +664,7 @@ func (s *posix) createFile(volume, path string) (f *os.File, err error) { } }() - if s.ioErrCount > maxAllowedIOError { + if atomic.LoadInt32(&s.ioErrCount) > maxAllowedIOError { return nil, errFaultyDisk } @@ -737,7 +737,7 @@ func (s *posix) PrepareFile(volume, path string, fileSize int64) (err error) { } }() - if s.ioErrCount > maxAllowedIOError { + if atomic.LoadInt32(&s.ioErrCount) > maxAllowedIOError { return errFaultyDisk } @@ -784,7 +784,7 @@ func (s *posix) AppendFile(volume, path string, buf []byte) (err error) { } }() - if s.ioErrCount > maxAllowedIOError { + if atomic.LoadInt32(&s.ioErrCount) > maxAllowedIOError { return errFaultyDisk } @@ -815,7 +815,7 @@ func (s *posix) StatFile(volume, path string) (file FileInfo, err error) { } }() - if s.ioErrCount > maxAllowedIOError { + if atomic.LoadInt32(&s.ioErrCount) > maxAllowedIOError { return FileInfo{}, errFaultyDisk } @@ -911,7 +911,7 @@ func (s *posix) DeleteFile(volume, path string) (err error) { } }() - if s.ioErrCount > maxAllowedIOError { + if atomic.LoadInt32(&s.ioErrCount) > maxAllowedIOError { return errFaultyDisk } @@ -951,7 +951,7 @@ func (s *posix) RenameFile(srcVolume, srcPath, dstVolume, dstPath string) (err e } }() - if s.ioErrCount > maxAllowedIOError { + if atomic.LoadInt32(&s.ioErrCount) > maxAllowedIOError { return errFaultyDisk } diff --git a/cmd/storage-rpc-client.go b/cmd/storage-rpc-client.go index 14dab823c..ee31587b1 100644 --- a/cmd/storage-rpc-client.go +++ b/cmd/storage-rpc-client.go @@ -28,7 +28,6 @@ import ( ) type networkStorage struct { - networkIOErrCount int32 // ref: https://golang.org/pkg/sync/atomic/#pkg-note-BUG rpcClient *AuthRPCClient } From 5621e6a4944ef9862c1dfda01c1835ad44aa4aa9 Mon Sep 17 00:00:00 2001 From: Dee Koder Date: Wed, 31 May 2017 11:53:04 -0700 Subject: [PATCH 50/80] Refactor service stop signal message. (#4428) --- cmd/service.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/cmd/service.go b/cmd/service.go index 908c45676..67fdd5f18 100644 --- a/cmd/service.go +++ b/cmd/service.go @@ -20,6 +20,7 @@ import ( "os" "os/exec" "syscall" + "time" ) // Type of service signals currently supported. @@ -79,7 +80,6 @@ func (m *ServerMux) handleServiceSignals() error { // We are usually done here, close global service done channel. globalServiceDoneCh <- struct{}{} } - // Wait for SIGTERM in a go-routine. trapCh := signalTrap(os.Interrupt, syscall.SIGTERM) go func(trapCh <-chan bool) { @@ -102,7 +102,11 @@ func (m *ServerMux) handleServiceSignals() error { } runExitFn(nil) case serviceStop: - log.Println("Gracefully stopping... (press Ctrl+C again to force)") + log.Println("Received signal to exit.") + go func() { + time.Sleep(serverShutdownPoll + time.Millisecond*100) + log.Println("Waiting for active connections to terminate - press Ctrl+C to quit immediately.") + }() if err := m.Close(); err != nil { errorIf(err, "Unable to close server gracefully") } From 9ba57a8df0808d2ea1c478a9c33a7810a850e8a0 Mon Sep 17 00:00:00 2001 From: Frank Wessels Date: Wed, 31 May 2017 20:03:32 -0700 Subject: [PATCH 51/80] Add errCorruptedFormat to list of ignored errors for metadata operations. (#4447) Fixes listing of objects where xl.json is empty or corrupted to skip to the next disk/server (issue 4354). --- cmd/xl-v1-metadata.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/xl-v1-metadata.go b/cmd/xl-v1-metadata.go index 588df3213..af8bdcd69 100644 --- a/cmd/xl-v1-metadata.go +++ b/cmd/xl-v1-metadata.go @@ -293,7 +293,7 @@ func pickValidXLMeta(metaArr []xlMetaV1, modTime time.Time) (xlMetaV1, error) { } // list of all errors that can be ignored in a metadata operation. -var objMetadataOpIgnoredErrs = append(baseIgnoredErrs, errDiskAccessDenied, errVolumeNotFound, errFileNotFound, errFileAccessDenied) +var objMetadataOpIgnoredErrs = append(baseIgnoredErrs, errDiskAccessDenied, errVolumeNotFound, errFileNotFound, errFileAccessDenied, errCorruptedFormat) // readXLMetaParts - returns the XL Metadata Parts from xl.json of one of the disks picked at random. func (xl xlObjects) readXLMetaParts(bucket, object string) (xlMetaParts []objectPartInfo, err error) { From 64f4dbc27284d4f94041c0ed917565a33f36674b Mon Sep 17 00:00:00 2001 From: Aditya Manthramurthy Date: Wed, 31 May 2017 20:33:13 -0700 Subject: [PATCH 52/80] Disable redirect of HTTP request to a HTTPS Minio server (#4454) Fixes #4452 --- appveyor.yml | 6 +++--- cmd/api-errors.go | 6 ++++++ cmd/server-mux.go | 24 ++++-------------------- cmd/server-mux_test.go | 39 +++++++++++++++++++++++++++++---------- 4 files changed, 42 insertions(+), 33 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 5a87b661a..06a1149b2 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -36,9 +36,9 @@ test_script: # Unit tests - ps: Add-AppveyorTest "Unit Tests" -Outcome Running - mkdir build\coverage - - 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 + - go test -v -timeout 17m -race github.com/minio/minio/cmd... + - go test -v -race github.com/minio/minio/pkg... + - go test -v -coverprofile=build\coverage\coverage.txt -covermode=atomic github.com/minio/minio/cmd - ps: Update-AppveyorTest "Unit Tests" -Outcome Passed after_test: diff --git a/cmd/api-errors.go b/cmd/api-errors.go index d8ef0b124..4a10bbdb5 100644 --- a/cmd/api-errors.go +++ b/cmd/api-errors.go @@ -149,6 +149,7 @@ const ( ErrAdminInvalidAccessKey ErrAdminInvalidSecretKey ErrAdminConfigNoQuorum + ErrInsecureClientRequest ) // error code to APIError structure, these fields carry respective @@ -618,6 +619,11 @@ var errorCodeResponse = map[APIErrorCode]APIError{ Description: "Configuration update failed because server quorum was not met", HTTPStatusCode: http.StatusServiceUnavailable, }, + ErrInsecureClientRequest: { + Code: "XMinioInsecureClientRequest", + Description: "Cannot respond to plain-text request from TLS-encrypted server", + HTTPStatusCode: http.StatusBadRequest, + }, // Add your error structure here. } diff --git a/cmd/server-mux.go b/cmd/server-mux.go index 4977c9d5a..e33a96790 100644 --- a/cmd/server-mux.go +++ b/cmd/server-mux.go @@ -449,25 +449,9 @@ func (m *ServerMux) ListenAndServe(certFile, keyFile string) (err error) { // All http requests start to be processed by httpHandler httpHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if tlsEnabled && r.TLS == nil { - // It is expected that r.Host might not have port - // for standard ports such as "80" and "443". - host, port, _ := net.SplitHostPort(r.Host) - if port == "" { - host = net.JoinHostPort(r.Host, globalMinioPort) - } else { - host = r.Host - } - // TLS is enabled but Request is not TLS configured - u := url.URL{ - Scheme: httpsScheme, - Opaque: r.URL.Opaque, - User: r.URL.User, - Host: host, - Path: r.URL.Path, - RawQuery: r.URL.RawQuery, - Fragment: r.URL.Fragment, - } - http.Redirect(w, r, u.String(), http.StatusTemporaryRedirect) + // TLS is enabled but request is not TLS + // configured - return error to client. + writeErrorResponse(w, ErrInsecureClientRequest, &url.URL{}) } else { // Return ServiceUnavailable for clients which are sending requests @@ -481,7 +465,7 @@ func (m *ServerMux) ListenAndServe(certFile, keyFile string) (err error) { } // Execute registered handlers, update currentReqs to keep - // tracks of current requests currently processed by the server + // track of concurrent requests processing on the server atomic.AddInt32(&m.currentReqs, 1) m.handler.ServeHTTP(w, r) atomic.AddInt32(&m.currentReqs, -1) diff --git a/cmd/server-mux_test.go b/cmd/server-mux_test.go index 22891386b..838017414 100644 --- a/cmd/server-mux_test.go +++ b/cmd/server-mux_test.go @@ -31,6 +31,7 @@ import ( "net/http" "os" "runtime" + "strings" "sync" "testing" "time" @@ -339,6 +340,11 @@ func TestServerListenAndServePlain(t *testing.T) { } func TestServerListenAndServeTLS(t *testing.T) { + _, err := newTestConfig(globalMinioDefaultRegion) + if err != nil { + t.Fatalf("Init Test config failed") + } + wait := make(chan struct{}) addr := net.JoinHostPort("127.0.0.1", getFreePort()) errc := make(chan error) @@ -354,7 +360,7 @@ func TestServerListenAndServeTLS(t *testing.T) { })) // Create a cert - err := createConfigDir() + err = createConfigDir() if err != nil { t.Fatal(err) } @@ -374,7 +380,6 @@ func TestServerListenAndServeTLS(t *testing.T) { wg := &sync.WaitGroup{} wg.Add(1) - // Keep trying the server until it's accepting connections go func() { tr := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, @@ -384,6 +389,7 @@ func TestServerListenAndServeTLS(t *testing.T) { Transport: tr, } okTLS := false + // Keep trying the server until it's accepting connections for !okTLS { res, _ := client.Get("https://" + addr) if res != nil && res.StatusCode == http.StatusOK { @@ -391,14 +397,27 @@ func TestServerListenAndServeTLS(t *testing.T) { } } - okNoTLS := false - for !okNoTLS { - res, _ := client.Get("http://" + addr) - // Without TLS we expect a re-direction from http to https - // And also the request is not rejected. - if res != nil && res.StatusCode == http.StatusOK && res.Request.URL.Scheme == httpsScheme { - okNoTLS = true - } + // Once a request succeeds, subsequent requests should + // work fine. + res, err := client.Get("http://" + addr) + if err != nil { + t.Errorf("Got unexpected error: %v", err) + } + // Without TLS we expect a Bad-Request response from the server. + if !(res != nil && res.StatusCode == http.StatusBadRequest && res.Request.URL.Scheme == httpScheme) { + t.Fatalf("Plaintext request to TLS server did not have expected response!") + } + + body, err := ioutil.ReadAll(res.Body) + if err != nil { + t.Errorf("Error reading body") + } + + // Check that the expected error is received. + bodyStr := string(body) + apiErr := getAPIError(ErrInsecureClientRequest) + if !(strings.Contains(bodyStr, apiErr.Code) && strings.Contains(bodyStr, apiErr.Description)) { + t.Fatalf("Plaintext request to TLS server did not have expected response body!") } wg.Done() }() From 18c4e5d3576524584b15b45162f3da0dd2e91bea Mon Sep 17 00:00:00 2001 From: poornas Date: Thu, 1 Jun 2017 09:43:20 -0700 Subject: [PATCH 53/80] Enable browser support for gateway (#4425) --- cmd/admin-handlers_test.go | 10 +++--- cmd/benchmark-utils_test.go | 10 +++--- cmd/bucket-handlers.go | 6 +++- cmd/bucket-handlers_test.go | 33 +++++++++++++++++++ cmd/bucket-policy-handlers_test.go | 2 +- cmd/credential.go | 4 +-- cmd/credential_test.go | 2 +- cmd/event-notifier_test.go | 8 ++--- cmd/format-config-v1_test.go | 6 ++-- cmd/fs-v1-metadata_test.go | 4 +-- cmd/fs-v1-multipart_test.go | 10 +++--- cmd/fs-v1.go | 2 +- cmd/fs-v1_test.go | 14 ++++----- cmd/gateway-azure.go | 6 ---- cmd/gateway-main.go | 46 ++++++++++++++++++++++----- cmd/gateway-main_test.go | 27 +++++++++++++++- cmd/gateway-router.go | 2 -- cmd/gateway-s3.go | 10 +----- cmd/jwt_test.go | 2 +- cmd/object-api-getobject_test.go | 6 ++-- cmd/object-api-getobjectinfo_test.go | 2 +- cmd/object-api-interface.go | 2 +- cmd/object-api-listobjects_test.go | 4 +-- cmd/object-api-multipart_test.go | 24 +++++++------- cmd/object-api-putobject_test.go | 12 +++---- cmd/object_api_suite_test.go | 36 ++++++++++----------- cmd/post-policy_test.go | 4 +-- cmd/test-utils_test.go | 2 +- cmd/web-handlers.go | 47 +++++++++++++++++++++++----- cmd/web-handlers_test.go | 22 ++++++------- cmd/xl-v1-bucket.go | 2 +- cmd/xl-v1-healing-common_test.go | 2 +- cmd/xl-v1-healing_test.go | 12 +++---- cmd/xl-v1-list-objects-heal_test.go | 4 +-- cmd/xl-v1-metadata_test.go | 4 +-- cmd/xl-v1-multipart_test.go | 2 +- cmd/xl-v1-object_test.go | 12 +++---- docs/gateway/README.md | 3 ++ 38 files changed, 260 insertions(+), 146 deletions(-) diff --git a/cmd/admin-handlers_test.go b/cmd/admin-handlers_test.go index 68ccdd80e..deba24f5b 100644 --- a/cmd/admin-handlers_test.go +++ b/cmd/admin-handlers_test.go @@ -731,7 +731,7 @@ func TestListObjectsHealHandler(t *testing.T) { } defer adminTestBed.TearDown() - err = adminTestBed.objLayer.MakeBucket("mybucket") + err = adminTestBed.objLayer.MakeBucketWithLocation("mybucket","") if err != nil { t.Fatalf("Failed to make bucket - %v", err) } @@ -859,7 +859,7 @@ func TestHealBucketHandler(t *testing.T) { } defer adminTestBed.TearDown() - err = adminTestBed.objLayer.MakeBucket("mybucket") + err = adminTestBed.objLayer.MakeBucketWithLocation("mybucket","") if err != nil { t.Fatalf("Failed to make bucket - %v", err) } @@ -936,7 +936,7 @@ func TestHealObjectHandler(t *testing.T) { // Create an object myobject under bucket mybucket. bucketName := "mybucket" objName := "myobject" - err = adminTestBed.objLayer.MakeBucket(bucketName) + err = adminTestBed.objLayer.MakeBucketWithLocation(bucketName,"") if err != nil { t.Fatalf("Failed to make bucket %s - %v", bucketName, err) } @@ -1067,7 +1067,7 @@ func TestHealUploadHandler(t *testing.T) { // Create an object myobject under bucket mybucket. bucketName := "mybucket" objName := "myobject" - err = adminTestBed.objLayer.MakeBucket(bucketName) + err = adminTestBed.objLayer.MakeBucketWithLocation(bucketName,"") if err != nil { t.Fatalf("Failed to make bucket %s - %v", bucketName, err) } @@ -1455,7 +1455,7 @@ func TestListHealUploadsHandler(t *testing.T) { } defer adminTestBed.TearDown() - err = adminTestBed.objLayer.MakeBucket("mybucket") + err = adminTestBed.objLayer.MakeBucketWithLocation("mybucket","") if err != nil { t.Fatalf("Failed to make bucket - %v", err) } diff --git a/cmd/benchmark-utils_test.go b/cmd/benchmark-utils_test.go index f871c844a..13aa24942 100644 --- a/cmd/benchmark-utils_test.go +++ b/cmd/benchmark-utils_test.go @@ -39,7 +39,7 @@ func runPutObjectBenchmark(b *testing.B, obj ObjectLayer, objSize int) { // obtains random bucket name. bucket := getRandomBucketName() // create bucket. - err = obj.MakeBucket(bucket) + err = obj.MakeBucketWithLocation(bucket, "") if err != nil { b.Fatal(err) } @@ -78,7 +78,7 @@ func runPutObjectPartBenchmark(b *testing.B, obj ObjectLayer, partSize int) { object := getRandomObjectName() // create bucket. - err = obj.MakeBucket(bucket) + err = obj.MakeBucketWithLocation(bucket, "") if err != nil { b.Fatal(err) } @@ -199,7 +199,7 @@ func runGetObjectBenchmark(b *testing.B, obj ObjectLayer, objSize int) { // obtains random bucket name. bucket := getRandomBucketName() // create bucket. - err = obj.MakeBucket(bucket) + err = obj.MakeBucketWithLocation(bucket, "") if err != nil { b.Fatal(err) } @@ -307,7 +307,7 @@ func runPutObjectBenchmarkParallel(b *testing.B, obj ObjectLayer, objSize int) { // obtains random bucket name. bucket := getRandomBucketName() // create bucket. - err = obj.MakeBucket(bucket) + err = obj.MakeBucketWithLocation(bucket, "") if err != nil { b.Fatal(err) } @@ -355,7 +355,7 @@ func runGetObjectBenchmarkParallel(b *testing.B, obj ObjectLayer, objSize int) { // obtains random bucket name. bucket := getRandomBucketName() // create bucket. - err = obj.MakeBucket(bucket) + err = obj.MakeBucketWithLocation(bucket, "") if err != nil { b.Fatal(err) } diff --git a/cmd/bucket-handlers.go b/cmd/bucket-handlers.go index c997c1dba..2d35d4269 100644 --- a/cmd/bucket-handlers.go +++ b/cmd/bucket-handlers.go @@ -83,6 +83,10 @@ func enforceBucketPolicy(bucket, action, resource, referer string, queryParams u // Check if the action is allowed on the bucket/prefix. func isBucketActionAllowed(action, bucket, prefix string) bool { + if globalBucketPolicies == nil { + return false + } + policy := globalBucketPolicies.GetBucketPolicy(bucket) if policy == nil { return false @@ -389,7 +393,7 @@ func (api objectAPIHandlers) PutBucketHandler(w http.ResponseWriter, r *http.Req defer bucketLock.Unlock() // Proceed to creating a bucket. - err := objectAPI.MakeBucket(bucket) + err := objectAPI.MakeBucketWithLocation(bucket, "") if err != nil { errorIf(err, "Unable to create a bucket.") writeErrorResponse(w, toAPIErrorCode(err), r.URL) diff --git a/cmd/bucket-handlers_test.go b/cmd/bucket-handlers_test.go index 75a7fe44a..bd57e7eb9 100644 --- a/cmd/bucket-handlers_test.go +++ b/cmd/bucket-handlers_test.go @@ -771,3 +771,36 @@ func testAPIDeleteMultipleObjectsHandler(obj ObjectLayer, instanceType, bucketNa // `ExecObjectLayerAPINilTest` manages the operation. ExecObjectLayerAPINilTest(t, nilBucket, nilObject, instanceType, apiRouter, nilReq) } + +func TestIsBucketActionAllowed(t *testing.T) { + ExecObjectLayerAPITest(t, testIsBucketActionAllowedHandler, []string{"BucketLocation"}) +} + +func testIsBucketActionAllowedHandler(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler, + credentials credential, t *testing.T) { + + testCases := []struct { + // input. + action string + bucket string + prefix string + isGlobalPoliciesNil bool + // flag indicating whether the test should pass. + shouldPass bool + }{ + {"s3:GetBucketLocation", "mybucket", "abc", true, false}, + {"s3:ListObject", "mybucket", "abc", false, false}, + } + for i, testCase := range testCases { + if testCase.isGlobalPoliciesNil { + globalBucketPolicies = nil + } else { + initBucketPolicies(obj) + } + isAllowed := isBucketActionAllowed(testCase.action, testCase.bucket, testCase.prefix) + if isAllowed != testCase.shouldPass { + t.Errorf("Case %d: Expected the response status to be `%t`, but instead found `%t`", i+1, testCase.shouldPass, isAllowed) + } + + } +} diff --git a/cmd/bucket-policy-handlers_test.go b/cmd/bucket-policy-handlers_test.go index a8ad8a3e1..5a625c2f4 100644 --- a/cmd/bucket-policy-handlers_test.go +++ b/cmd/bucket-policy-handlers_test.go @@ -251,7 +251,7 @@ func testPutBucketPolicyHandler(obj ObjectLayer, instanceType, bucketName string initBucketPolicies(obj) bucketName1 := fmt.Sprintf("%s-1", bucketName) - if err := obj.MakeBucket(bucketName1); err != nil { + if err := obj.MakeBucketWithLocation(bucketName1, ""); err != nil { t.Fatal(err) } diff --git a/cmd/credential.go b/cmd/credential.go index a30893b32..769707d84 100644 --- a/cmd/credential.go +++ b/cmd/credential.go @@ -28,14 +28,14 @@ const ( accessKeyMinLen = 5 accessKeyMaxLen = 20 secretKeyMinLen = 8 - secretKeyMaxLenAmazon = 40 + secretKeyMaxLenAmazon = 100 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") + errInvalidSecretKeyLength = errors.New("Invalid secret key, secret key should be 8 to 100 characters in length") ) var secretKeyMaxLen = secretKeyMaxLenAmazon diff --git a/cmd/credential_test.go b/cmd/credential_test.go index 4d30519d3..b35d703de 100644 --- a/cmd/credential_test.go +++ b/cmd/credential_test.go @@ -42,7 +42,7 @@ func TestCreateCredential(t *testing.T) { // Secret key too small. {"myuser", "pass", false, errInvalidSecretKeyLength}, // Secret key too long. - {"myuser", "pass1234567890123456789012345678901234567", false, errInvalidSecretKeyLength}, + {"myuser", "pass1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890", false, errInvalidSecretKeyLength}, // Success when access key contains leading/trailing spaces. {" user ", cred.SecretKey, true, nil}, {"myuser", "mypassword", true, nil}, diff --git a/cmd/event-notifier_test.go b/cmd/event-notifier_test.go index f8e74b188..4b2087417 100644 --- a/cmd/event-notifier_test.go +++ b/cmd/event-notifier_test.go @@ -45,7 +45,7 @@ func TestInitEventNotifierFaultyDisks(t *testing.T) { } bucketName := "bucket" - if err := obj.MakeBucket(bucketName); err != nil { + if err := obj.MakeBucketWithLocation(bucketName, ""); err != nil { t.Fatal("Unexpected error:", err) } @@ -343,7 +343,7 @@ func TestInitEventNotifier(t *testing.T) { } // create bucket - if err := obj.MakeBucket(bucketName); err != nil { + if err := obj.MakeBucketWithLocation(bucketName, ""); err != nil { t.Fatal("Unexpected error:", err) } @@ -408,7 +408,7 @@ func TestListenBucketNotification(t *testing.T) { objectName := "object" // Create the bucket to listen on - if err := obj.MakeBucket(bucketName); err != nil { + if err := obj.MakeBucketWithLocation(bucketName, ""); err != nil { t.Fatal("Unexpected error:", err) } @@ -518,7 +518,7 @@ func TestAddRemoveBucketListenerConfig(t *testing.T) { // Make a bucket to store topicConfigs. randBucket := getRandomBucketName() - if err := obj.MakeBucket(randBucket); err != nil { + if err := obj.MakeBucketWithLocation(randBucket, ""); err != nil { t.Fatalf("Failed to make bucket %s", randBucket) } diff --git a/cmd/format-config-v1_test.go b/cmd/format-config-v1_test.go index c6761ec74..6e00adda6 100644 --- a/cmd/format-config-v1_test.go +++ b/cmd/format-config-v1_test.go @@ -223,7 +223,7 @@ func prepareFormatXLHealFreshDisks(obj ObjectLayer) ([]StorageAPI, error) { var err error xl := obj.(*xlObjects) - err = obj.MakeBucket("bucket") + err = obj.MakeBucketWithLocation("bucket", "") if err != nil { return []StorageAPI{}, err } @@ -346,7 +346,7 @@ func TestFormatXLHealCorruptedDisks(t *testing.T) { xl := obj.(*xlObjects) - err = obj.MakeBucket("bucket") + err = obj.MakeBucketWithLocation("bucket", "") if err != nil { t.Fatal(err) } @@ -419,7 +419,7 @@ func TestFormatXLReorderByInspection(t *testing.T) { xl := obj.(*xlObjects) - err = obj.MakeBucket("bucket") + err = obj.MakeBucketWithLocation("bucket", "") if err != nil { t.Fatal(err) } diff --git a/cmd/fs-v1-metadata_test.go b/cmd/fs-v1-metadata_test.go index 9d960f067..1e71acb63 100644 --- a/cmd/fs-v1-metadata_test.go +++ b/cmd/fs-v1-metadata_test.go @@ -48,7 +48,7 @@ func TestReadFSMetadata(t *testing.T) { bucketName := "bucket" objectName := "object" - if err := obj.MakeBucket(bucketName); err != nil { + if err := obj.MakeBucketWithLocation(bucketName, ""); err != nil { t.Fatal("Unexpected err: ", err) } sha256sum := "" @@ -85,7 +85,7 @@ func TestWriteFSMetadata(t *testing.T) { bucketName := "bucket" objectName := "object" - if err := obj.MakeBucket(bucketName); err != nil { + if err := obj.MakeBucketWithLocation(bucketName, ""); err != nil { t.Fatal("Unexpected err: ", err) } sha256sum := "" diff --git a/cmd/fs-v1-multipart_test.go b/cmd/fs-v1-multipart_test.go index 75ef537a5..c69807440 100644 --- a/cmd/fs-v1-multipart_test.go +++ b/cmd/fs-v1-multipart_test.go @@ -33,7 +33,7 @@ func TestFSWriteUploadJSON(t *testing.T) { bucketName := "bucket" objectName := "object" - obj.MakeBucket(bucketName) + obj.MakeBucketWithLocation(bucketName, "") _, err := obj.NewMultipartUpload(bucketName, objectName, nil) if err != nil { t.Fatal("Unexpected err: ", err) @@ -60,7 +60,7 @@ func TestNewMultipartUploadFaultyDisk(t *testing.T) { bucketName := "bucket" objectName := "object" - if err := obj.MakeBucket(bucketName); err != nil { + if err := obj.MakeBucketWithLocation(bucketName, ""); err != nil { t.Fatal("Cannot create bucket, err: ", err) } @@ -91,7 +91,7 @@ func TestPutObjectPartFaultyDisk(t *testing.T) { data := []byte("12345") dataLen := int64(len(data)) - if err = obj.MakeBucket(bucketName); err != nil { + if err = obj.MakeBucketWithLocation(bucketName, ""); err != nil { t.Fatal("Cannot create bucket, err: ", err) } @@ -122,7 +122,7 @@ func TestCompleteMultipartUploadFaultyDisk(t *testing.T) { objectName := "object" data := []byte("12345") - if err := obj.MakeBucket(bucketName); err != nil { + if err := obj.MakeBucketWithLocation(bucketName, ""); err != nil { t.Fatal("Cannot create bucket, err: ", err) } @@ -161,7 +161,7 @@ func TestListMultipartUploadsFaultyDisk(t *testing.T) { objectName := "object" data := []byte("12345") - if err := obj.MakeBucket(bucketName); err != nil { + if err := obj.MakeBucketWithLocation(bucketName, ""); err != nil { t.Fatal("Cannot create bucket, err: ", err) } diff --git a/cmd/fs-v1.go b/cmd/fs-v1.go index b33a700fb..590b7d1ad 100644 --- a/cmd/fs-v1.go +++ b/cmd/fs-v1.go @@ -302,7 +302,7 @@ func (fs fsObjects) statBucketDir(bucket string) (os.FileInfo, error) { // MakeBucket - create a new bucket, returns if it // already exists. -func (fs fsObjects) MakeBucket(bucket string) error { +func (fs fsObjects) MakeBucketWithLocation(bucket, location string) error { bucketDir, err := fs.getBucketDir(bucket) if err != nil { return toObjectErr(err, bucket) diff --git a/cmd/fs-v1_test.go b/cmd/fs-v1_test.go index 71962e99c..a79f0327b 100644 --- a/cmd/fs-v1_test.go +++ b/cmd/fs-v1_test.go @@ -66,7 +66,7 @@ func TestFSShutdown(t *testing.T) { obj := initFSObjects(disk, t) fs := obj.(*fsObjects) objectContent := "12345" - obj.MakeBucket(bucketName) + obj.MakeBucketWithLocation(bucketName, "") sha256sum := "" obj.PutObject(bucketName, objectName, int64(len(objectContent)), bytes.NewReader([]byte(objectContent)), nil, sha256sum) return fs, disk @@ -498,7 +498,7 @@ func TestFSGetBucketInfo(t *testing.T) { fs := obj.(*fsObjects) bucketName := "bucket" - obj.MakeBucket(bucketName) + obj.MakeBucketWithLocation(bucketName, "") // Test with valid parameters info, err := fs.GetBucketInfo(bucketName) @@ -533,7 +533,7 @@ func TestFSPutObject(t *testing.T) { bucketName := "bucket" objectName := "1/2/3/4/object" - if err := obj.MakeBucket(bucketName); err != nil { + if err := obj.MakeBucketWithLocation(bucketName, ""); err != nil { t.Fatal(err) } @@ -603,7 +603,7 @@ func TestFSDeleteObject(t *testing.T) { bucketName := "bucket" objectName := "object" - obj.MakeBucket(bucketName) + obj.MakeBucketWithLocation(bucketName, "") sha256sum := "" obj.PutObject(bucketName, objectName, int64(len("abcd")), bytes.NewReader([]byte("abcd")), nil, sha256sum) @@ -648,7 +648,7 @@ func TestFSDeleteBucket(t *testing.T) { fs := obj.(*fsObjects) bucketName := "bucket" - err := obj.MakeBucket(bucketName) + err := obj.MakeBucketWithLocation(bucketName, "") if err != nil { t.Fatal("Unexpected error: ", err) } @@ -666,7 +666,7 @@ func TestFSDeleteBucket(t *testing.T) { t.Fatal("Unexpected error: ", err) } - obj.MakeBucket(bucketName) + obj.MakeBucketWithLocation(bucketName, "") // Delete bucker should get error disk not found. removeAll(disk) @@ -687,7 +687,7 @@ func TestFSListBuckets(t *testing.T) { fs := obj.(*fsObjects) bucketName := "bucket" - if err := obj.MakeBucket(bucketName); err != nil { + if err := obj.MakeBucketWithLocation(bucketName, ""); err != nil { t.Fatal("Unexpected error: ", err) } diff --git a/cmd/gateway-azure.go b/cmd/gateway-azure.go index 39e7f4065..16f762f52 100644 --- a/cmd/gateway-azure.go +++ b/cmd/gateway-azure.go @@ -183,12 +183,6 @@ func (a *azureObjects) StorageInfo() StorageInfo { return StorageInfo{} } -// MakeBucket - Create a new container on azure backend. -func (a *azureObjects) MakeBucket(bucket string) error { - // will never be called, only satisfy ObjectLayer interface - return traceError(NotImplemented{}) -} - // MakeBucketWithLocation - Create a new container on azure backend. func (a *azureObjects) MakeBucketWithLocation(bucket, location string) error { err := a.client.CreateContainer(bucket, storage.ContainerAccessTypePrivate) diff --git a/cmd/gateway-main.go b/cmd/gateway-main.go index 9ca9d94e1..cd48525b6 100644 --- a/cmd/gateway-main.go +++ b/cmd/gateway-main.go @@ -19,12 +19,11 @@ package cmd import ( "errors" "fmt" + "github.com/gorilla/mux" + "github.com/minio/cli" "net/url" "os" "strings" - - "github.com/gorilla/mux" - "github.com/minio/cli" ) var gatewayTemplate = `NAME: @@ -45,6 +44,9 @@ ENVIRONMENT VARIABLES: MINIO_ACCESS_KEY: Username or access key of your storage backend. MINIO_SECRET_KEY: Password or secret key of your storage backend. + BROWSER: + MINIO_BROWSER: To disable web browser access, set this value to "off". + EXAMPLES: 1. Start minio gateway server for Azure Blob Storage backend. $ export MINIO_ACCESS_KEY=azureaccountname @@ -96,12 +98,27 @@ func mustGetGatewayCredsFromEnv() (accessKey, secretKey string) { return accessKey, secretKey } +// Set browser setting from environment variables +func mustSetBrowserSettingFromEnv(){ + if browser := os.Getenv("MINIO_BROWSER"); browser != "" { + browserFlag, err := ParseBrowserFlag(browser) + if err != nil { + fatalIf(errors.New("invalid value"), "Unknown value ‘%s’ in MINIO_BROWSER environment variable.", browser) + } + + // browser Envs are set globally, this does not represent + // if browser is turned off or on. + globalIsEnvBrowser = true + globalIsBrowserEnabled = bool(browserFlag) + } +} // Initialize gateway layer depending on the backend type. // Supported backend types are // // - Azure Blob Storage. // - Add your favorite backend here. func newGatewayLayer(backendType, endpoint, accessKey, secretKey string, secure bool) (GatewayLayer, error) { + switch gatewayBackend(backendType) { case azureBackend: return newAzureLayer(endpoint, accessKey, secretKey, secure) @@ -171,11 +188,11 @@ func gatewayMain(ctx *cli.Context) { // Fetch access and secret key from env. accessKey, secretKey := mustGetGatewayCredsFromEnv() + // Fetch browser env setting + mustSetBrowserSettingFromEnv() + // 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" + newGatewayConfig(accessKey, secretKey, globalMinioDefaultRegion) // Get quiet flag from command line argument. @@ -214,6 +231,12 @@ func gatewayMain(ctx *cli.Context) { initNSLock(false) // Enable local namespace lock. router := mux.NewRouter().SkipClean(true) + + // Register web router when its enabled. + if globalIsBrowserEnabled { + aerr := registerWebRouter(router) + fatalIf(aerr, "Unable to configure web browser") + } registerGatewayAPIRouter(router, newObject) var handlerFns = []HandlerFunc{ @@ -224,6 +247,13 @@ func gatewayMain(ctx *cli.Context) { // Adds 'crossdomain.xml' policy handler to serve legacy flash clients. setCrossDomainPolicy, // Validates all incoming requests to have a valid date header. + // Redirect some pre-defined browser request paths to a static location prefix. + setBrowserRedirectHandler, + // Validates if incoming request is for restricted buckets. + setPrivateBucketHandler, + // Adds cache control for all browser requests. + setBrowserCacheControlHandler, + // Validates all incoming requests to have a valid date header. setTimeValidityHandler, // CORS setting for all browser API requests. setCorsHandler, @@ -234,6 +264,8 @@ func gatewayMain(ctx *cli.Context) { // routes them accordingly. Client receives a HTTP error for // invalid/unsupported signatures. setAuthHandler, + // Add new handlers here. + } apiServer := NewServerMux(serverAddr, registerHandlers(router, handlerFns...)) diff --git a/cmd/gateway-main_test.go b/cmd/gateway-main_test.go index dc1b986d5..1779725bf 100644 --- a/cmd/gateway-main_test.go +++ b/cmd/gateway-main_test.go @@ -16,7 +16,10 @@ package cmd -import "testing" +import ( + "os" + "testing" +) // Test parseGatewayEndpoint func TestParseGatewayEndpoint(t *testing.T) { @@ -48,3 +51,25 @@ func TestParseGatewayEndpoint(t *testing.T) { } } } + +func TestSetBrowserFromEnv(t *testing.T) { + browser := os.Getenv("MINIO_BROWSER") + + os.Setenv("MINIO_BROWSER", "on") + mustSetBrowserSettingFromEnv() + if globalIsBrowserEnabled != true { + t.Errorf("Expected the response status to be `%t`, but instead found `%t`", globalIsBrowserEnabled, false) + } + + os.Setenv("MINIO_BROWSER", "off") + mustSetBrowserSettingFromEnv() + if globalIsBrowserEnabled != false { + t.Errorf("Expected the response status to be `%t`, but instead found `%t`", globalIsBrowserEnabled, true) + } + os.Setenv("MINIO_BROWSER", "") + mustSetBrowserSettingFromEnv() + if globalIsBrowserEnabled != false { + t.Errorf("Expected the response status to be `%t`, but instead found `%t`", globalIsBrowserEnabled, true) + } + os.Setenv("MINIO_BROWSER", browser) +} diff --git a/cmd/gateway-router.go b/cmd/gateway-router.go index b1d16f310..b286ed6ed 100644 --- a/cmd/gateway-router.go +++ b/cmd/gateway-router.go @@ -27,8 +27,6 @@ import ( type GatewayLayer interface { ObjectLayer - MakeBucketWithLocation(bucket, location string) error - AnonGetObject(bucket, object string, startOffset int64, length int64, writer io.Writer) (err error) AnonGetObjectInfo(bucket, object string) (objInfo ObjectInfo, err error) diff --git a/cmd/gateway-s3.go b/cmd/gateway-s3.go index f2c2e49b5..027b1071a 100644 --- a/cmd/gateway-s3.go +++ b/cmd/gateway-s3.go @@ -17,12 +17,11 @@ package cmd import ( + "encoding/hex" "io" "net/http" "path" - "encoding/hex" - minio "github.com/minio/minio-go" "github.com/minio/minio-go/pkg/policy" ) @@ -133,12 +132,6 @@ func (l *s3Objects) StorageInfo() StorageInfo { return StorageInfo{} } -// MakeBucket creates a new container on S3 backend. -func (l *s3Objects) MakeBucket(bucket string) error { - // will never be called, only satisfy ObjectLayer interface - return traceError(NotImplemented{}) -} - // MakeBucket creates a new container on S3 backend. func (l *s3Objects) MakeBucketWithLocation(bucket, location string) error { err := l.Client.MakeBucket(bucket, location) @@ -565,6 +558,5 @@ func (l *s3Objects) DeleteBucketPolicies(bucket string) error { if err := l.Client.PutBucketPolicy(bucket, policy.BucketAccessPolicy{}); err != nil { return s3ToObjectError(traceError(err), bucket, "") } - return nil } diff --git a/cmd/jwt_test.go b/cmd/jwt_test.go index 7a101252e..3edc25c28 100644 --- a/cmd/jwt_test.go +++ b/cmd/jwt_test.go @@ -44,7 +44,7 @@ func testAuthenticate(authType string, t *testing.T) { // Secret key too small. {"myuser", "pass", errInvalidSecretKeyLength}, // Secret key too long. - {"myuser", "pass1234567890123456789012345678901234567", errInvalidSecretKeyLength}, + {"myuser", "pass1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890", errInvalidSecretKeyLength}, // Authentication error. {"myuser", "mypassword", errInvalidAccessKeyID}, // Authentication error. diff --git a/cmd/object-api-getobject_test.go b/cmd/object-api-getobject_test.go index 1e6b429a7..9f1dc89a0 100644 --- a/cmd/object-api-getobject_test.go +++ b/cmd/object-api-getobject_test.go @@ -39,7 +39,7 @@ func testGetObject(obj ObjectLayer, instanceType string, t TestErrHandler) { bucketName := getRandomBucketName() objectName := "test-object" // create bucket. - err := obj.MakeBucket(bucketName) + err := obj.MakeBucketWithLocation(bucketName, "") // Stop the test if creation of the bucket fails. if err != nil { t.Fatalf("%s : %s", instanceType, err.Error()) @@ -192,7 +192,7 @@ func testGetObjectPermissionDenied(obj ObjectLayer, instanceType string, disks [ // Setup for the tests. bucketName := getRandomBucketName() // create bucket. - err := obj.MakeBucket(bucketName) + err := obj.MakeBucketWithLocation(bucketName, "") // Stop the test if creation of the bucket fails. if err != nil { t.Fatalf("%s : %s", instanceType, err.Error()) @@ -303,7 +303,7 @@ func testGetObjectDiskNotFound(obj ObjectLayer, instanceType string, disks []str bucketName := getRandomBucketName() objectName := "test-object" // create bucket. - err := obj.MakeBucket(bucketName) + err := obj.MakeBucketWithLocation(bucketName, "") // Stop the test if creation of the bucket fails. if err != nil { t.Fatalf("%s : %s", instanceType, err.Error()) diff --git a/cmd/object-api-getobjectinfo_test.go b/cmd/object-api-getobjectinfo_test.go index bdba72689..6110e2737 100644 --- a/cmd/object-api-getobjectinfo_test.go +++ b/cmd/object-api-getobjectinfo_test.go @@ -29,7 +29,7 @@ func TestGetObjectInfo(t *testing.T) { // Testing GetObjectInfo(). func testGetObjectInfo(obj ObjectLayer, instanceType string, t TestErrHandler) { // This bucket is used for testing getObjectInfo operations. - err := obj.MakeBucket("test-getobjectinfo") + err := obj.MakeBucketWithLocation("test-getobjectinfo", "") if err != nil { t.Fatalf("%s : %s", instanceType, err.Error()) } diff --git a/cmd/object-api-interface.go b/cmd/object-api-interface.go index 2e4e26df7..36a54b8aa 100644 --- a/cmd/object-api-interface.go +++ b/cmd/object-api-interface.go @@ -25,7 +25,7 @@ type ObjectLayer interface { StorageInfo() StorageInfo // Bucket operations. - MakeBucket(bucket string) error + MakeBucketWithLocation(bucket string, location string) error GetBucketInfo(bucket string) (bucketInfo BucketInfo, err error) ListBuckets() (buckets []BucketInfo, err error) DeleteBucket(bucket string) error diff --git a/cmd/object-api-listobjects_test.go b/cmd/object-api-listobjects_test.go index 560a36101..90c2b8dda 100644 --- a/cmd/object-api-listobjects_test.go +++ b/cmd/object-api-listobjects_test.go @@ -41,7 +41,7 @@ func testListObjects(obj ObjectLayer, instanceType string, t TestErrHandler) { "empty-bucket", } for _, bucket := range testBuckets { - err := obj.MakeBucket(bucket) + err := obj.MakeBucketWithLocation(bucket, "") if err != nil { t.Fatalf("%s : %s", instanceType, err.Error()) } @@ -599,7 +599,7 @@ func BenchmarkListObjects(b *testing.B) { bucket := "ls-benchmark-bucket" // Create a bucket. - err = obj.MakeBucket(bucket) + err = obj.MakeBucketWithLocation(bucket, "") if err != nil { b.Fatal(err) } diff --git a/cmd/object-api-multipart_test.go b/cmd/object-api-multipart_test.go index 263bb36ae..36be3f8a2 100644 --- a/cmd/object-api-multipart_test.go +++ b/cmd/object-api-multipart_test.go @@ -52,7 +52,7 @@ func testObjectNewMultipartUpload(obj ObjectLayer, instanceType string, t TestEr } // Create bucket before intiating NewMultipartUpload. - err = obj.MakeBucket(bucket) + err = obj.MakeBucketWithLocation(bucket, "") if err != nil { // failed to create newbucket, abort. t.Fatalf("%s : %s", instanceType, err.Error()) @@ -91,7 +91,7 @@ func testObjectAbortMultipartUpload(obj ObjectLayer, instanceType string, t Test object := "minio-object" // Create bucket before intiating NewMultipartUpload. - err := obj.MakeBucket(bucket) + err := obj.MakeBucketWithLocation(bucket, "") if err != nil { // failed to create newbucket, abort. t.Fatalf("%s : %s", instanceType, err.Error()) @@ -137,7 +137,7 @@ func testObjectAPIIsUploadIDExists(obj ObjectLayer, instanceType string, t TestE object := "minio-object" // Create bucket before intiating NewMultipartUpload. - err := obj.MakeBucket(bucket) + err := obj.MakeBucketWithLocation(bucket, "") if err != nil { // Failed to create newbucket, abort. t.Fatalf("%s : %s", instanceType, err.Error()) @@ -173,7 +173,7 @@ func testPutObjectPartDiskNotFound(obj ObjectLayer, instanceType string, disks [ // objectNames[0]. // uploadIds [0]. // Create bucket before intiating NewMultipartUpload. - err := obj.MakeBucket(bucketNames[0]) + err := obj.MakeBucketWithLocation(bucketNames[0], "") if err != nil { // Failed to create newbucket, abort. t.Fatalf("%s : %s", instanceType, err.Error()) @@ -253,7 +253,7 @@ func testObjectAPIPutObjectPart(obj ObjectLayer, instanceType string, t TestErrH object := "minio-object" // Create bucket before intiating NewMultipartUpload. - err := obj.MakeBucket(bucket) + err := obj.MakeBucketWithLocation(bucket, "") if err != nil { // Failed to create newbucket, abort. t.Fatalf("%s : %s", instanceType, err.Error()) @@ -265,7 +265,7 @@ func testObjectAPIPutObjectPart(obj ObjectLayer, instanceType string, t TestErrH t.Fatalf("%s : %s", instanceType, err.Error()) } // Creating a dummy bucket for tests. - err = obj.MakeBucket("unused-bucket") + err = obj.MakeBucketWithLocation("unused-bucket", "") if err != nil { // Failed to create newbucket, abort. t.Fatalf("%s : %s", instanceType, err.Error()) @@ -386,7 +386,7 @@ func testListMultipartUploads(obj ObjectLayer, instanceType string, t TestErrHan // objectNames[0]. // uploadIds [0]. // Create bucket before initiating NewMultipartUpload. - err := obj.MakeBucket(bucketNames[0]) + err := obj.MakeBucketWithLocation(bucketNames[0], "") if err != nil { // Failed to create newbucket, abort. t.Fatalf("%s : %s", instanceType, err.Error()) @@ -404,7 +404,7 @@ func testListMultipartUploads(obj ObjectLayer, instanceType string, t TestErrHan // objectNames[0]. // uploadIds [1-3]. // Bucket to test for mutiple upload Id's for a given object. - err = obj.MakeBucket(bucketNames[1]) + err = obj.MakeBucketWithLocation(bucketNames[1], "") if err != nil { // Failed to create newbucket, abort. t.Fatalf("%s : %s", instanceType, err.Error()) @@ -425,7 +425,7 @@ func testListMultipartUploads(obj ObjectLayer, instanceType string, t TestErrHan // bucketnames[2]. // objectNames[0-2]. // uploadIds [4-9]. - err = obj.MakeBucket(bucketNames[2]) + err = obj.MakeBucketWithLocation(bucketNames[2], "") if err != nil { // Failed to create newbucket, abort. t.Fatalf("%s : %s", instanceType, err.Error()) @@ -1288,7 +1288,7 @@ func testListObjectPartsDiskNotFound(obj ObjectLayer, instanceType string, disks // objectNames[0]. // uploadIds [0]. // Create bucket before intiating NewMultipartUpload. - err := obj.MakeBucket(bucketNames[0]) + err := obj.MakeBucketWithLocation(bucketNames[0], "") if err != nil { // Failed to create newbucket, abort. t.Fatalf("%s : %s", instanceType, err.Error()) @@ -1531,7 +1531,7 @@ func testListObjectParts(obj ObjectLayer, instanceType string, t TestErrHandler) // objectNames[0]. // uploadIds [0]. // Create bucket before intiating NewMultipartUpload. - err := obj.MakeBucket(bucketNames[0]) + err := obj.MakeBucketWithLocation(bucketNames[0], "") if err != nil { // Failed to create newbucket, abort. t.Fatalf("%s : %s", instanceType, err.Error()) @@ -1769,7 +1769,7 @@ func testObjectCompleteMultipartUpload(obj ObjectLayer, instanceType string, t T // objectNames[0]. // uploadIds [0]. // Create bucket before intiating NewMultipartUpload. - err = obj.MakeBucket(bucketNames[0]) + err = obj.MakeBucketWithLocation(bucketNames[0], "") if err != nil { // Failed to create newbucket, abort. t.Fatalf("%s : %s", instanceType, err) diff --git a/cmd/object-api-putobject_test.go b/cmd/object-api-putobject_test.go index 4a8e702ce..cbd377f22 100644 --- a/cmd/object-api-putobject_test.go +++ b/cmd/object-api-putobject_test.go @@ -44,14 +44,14 @@ func testObjectAPIPutObject(obj ObjectLayer, instanceType string, t TestErrHandl object := "minio-object" // Create bucket. - err := obj.MakeBucket(bucket) + err := obj.MakeBucketWithLocation(bucket, "") if err != nil { // Failed to create newbucket, abort. t.Fatalf("%s : %s", instanceType, err.Error()) } // Creating a dummy bucket for tests. - err = obj.MakeBucket("unused-bucket") + err = obj.MakeBucketWithLocation("unused-bucket", "") if err != nil { // Failed to create newbucket, abort. t.Fatalf("%s : %s", instanceType, err.Error()) @@ -189,14 +189,14 @@ func testObjectAPIPutObjectDiskNotFound(obj ObjectLayer, instanceType string, di object := "minio-object" // Create bucket. - err := obj.MakeBucket(bucket) + err := obj.MakeBucketWithLocation(bucket, "") if err != nil { // Failed to create newbucket, abort. t.Fatalf("%s : %s", instanceType, err.Error()) } // Creating a dummy bucket for tests. - err = obj.MakeBucket("unused-bucket") + err = obj.MakeBucketWithLocation("unused-bucket", "") if err != nil { // Failed to create newbucket, abort. t.Fatalf("%s : %s", instanceType, err.Error()) @@ -303,7 +303,7 @@ func testObjectAPIPutObjectStaleFiles(obj ObjectLayer, instanceType string, disk object := "minio-object" // Create bucket. - err := obj.MakeBucket(bucket) + err := obj.MakeBucketWithLocation(bucket, "") if err != nil { // Failed to create newbucket, abort. t.Fatalf("%s : %s", instanceType, err.Error()) @@ -338,7 +338,7 @@ func testObjectAPIMultipartPutObjectStaleFiles(obj ObjectLayer, instanceType str object := "minio-object" // Create bucket. - err := obj.MakeBucket(bucket) + err := obj.MakeBucketWithLocation(bucket, "") if err != nil { // Failed to create newbucket, abort. t.Fatalf("%s : %s", instanceType, err.Error()) diff --git a/cmd/object_api_suite_test.go b/cmd/object_api_suite_test.go index 4fe4661ca..b7de80f98 100644 --- a/cmd/object_api_suite_test.go +++ b/cmd/object_api_suite_test.go @@ -79,7 +79,7 @@ func (s *ObjectLayerAPISuite) TestMakeBucket(c *C) { // Tests validate bucket creation. func testMakeBucket(obj ObjectLayer, instanceType string, c TestErrHandler) { - err := obj.MakeBucket("bucket-unknown") + err := obj.MakeBucketWithLocation("bucket-unknown", "") if err != nil { c.Fatalf("%s: %s", instanceType, err) } @@ -92,7 +92,7 @@ func (s *ObjectLayerAPISuite) TestMultipartObjectCreation(c *C) { // Tests validate creation of part files during Multipart operation. func testMultipartObjectCreation(obj ObjectLayer, instanceType string, c TestErrHandler) { - err := obj.MakeBucket("bucket") + err := obj.MakeBucketWithLocation("bucket", "") if err != nil { c.Fatalf("%s: %s", instanceType, err) } @@ -135,7 +135,7 @@ func (s *ObjectLayerAPISuite) TestMultipartObjectAbort(c *C) { // Tests validate abortion of Multipart operation. func testMultipartObjectAbort(obj ObjectLayer, instanceType string, c TestErrHandler) { - err := obj.MakeBucket("bucket") + err := obj.MakeBucketWithLocation("bucket", "") if err != nil { c.Fatalf("%s: %s", instanceType, err) } @@ -180,7 +180,7 @@ func (s *ObjectLayerAPISuite) TestMultipleObjectCreation(c *C) { // Tests validate object creation. func testMultipleObjectCreation(obj ObjectLayer, instanceType string, c TestErrHandler) { objects := make(map[string][]byte) - err := obj.MakeBucket("bucket") + err := obj.MakeBucketWithLocation("bucket", "") if err != nil { c.Fatalf("%s: %s", instanceType, err) } @@ -235,7 +235,7 @@ func (s *ObjectLayerAPISuite) TestPaging(c *C) { // Tests validate creation of objects and the order of listing using various filters for ListObjects operation. func testPaging(obj ObjectLayer, instanceType string, c TestErrHandler) { - obj.MakeBucket("bucket") + obj.MakeBucketWithLocation("bucket", "") result, err := obj.ListObjects("bucket", "", "", "", 0) if err != nil { c.Fatalf("%s: %s", instanceType, err) @@ -438,7 +438,7 @@ func (s *ObjectLayerAPISuite) TestObjectOverwriteWorks(c *C) { // Tests validate overwriting of an existing object. func testObjectOverwriteWorks(obj ObjectLayer, instanceType string, c TestErrHandler) { - err := obj.MakeBucket("bucket") + err := obj.MakeBucketWithLocation("bucket", "") if err != nil { c.Fatalf("%s: %s", instanceType, err) } @@ -488,11 +488,11 @@ func (s *ObjectLayerAPISuite) TestBucketRecreateFails(c *C) { // Tests validate that recreation of the bucket fails. func testBucketRecreateFails(obj ObjectLayer, instanceType string, c TestErrHandler) { - err := obj.MakeBucket("string") + err := obj.MakeBucketWithLocation("string", "") if err != nil { c.Fatalf("%s: %s", instanceType, err) } - err = obj.MakeBucket("string") + err = obj.MakeBucketWithLocation("string", "") if err == nil { c.Fatalf("%s: Expected error but found nil.", instanceType) } @@ -513,7 +513,7 @@ func testPutObject(obj ObjectLayer, instanceType string, c TestErrHandler) { length := int64(len(content)) readerEOF := newTestReaderEOF(content) readerNoEOF := newTestReaderNoEOF(content) - err := obj.MakeBucket("bucket") + err := obj.MakeBucketWithLocation("bucket", "") if err != nil { c.Fatalf("%s: %s", instanceType, err) } @@ -552,7 +552,7 @@ func (s *ObjectLayerAPISuite) TestPutObjectInSubdir(c *C) { // Tests validate PutObject with subdirectory prefix. func testPutObjectInSubdir(obj ObjectLayer, instanceType string, c TestErrHandler) { - err := obj.MakeBucket("bucket") + err := obj.MakeBucketWithLocation("bucket", "") if err != nil { c.Fatalf("%s: %s", instanceType, err) } @@ -593,7 +593,7 @@ func testListBuckets(obj ObjectLayer, instanceType string, c TestErrHandler) { } // add one and test exists. - err = obj.MakeBucket("bucket1") + err = obj.MakeBucketWithLocation("bucket1", "") if err != nil { c.Fatalf("%s: %s", instanceType, err) } @@ -607,7 +607,7 @@ func testListBuckets(obj ObjectLayer, instanceType string, c TestErrHandler) { } // add two and test exists. - err = obj.MakeBucket("bucket2") + err = obj.MakeBucketWithLocation("bucket2", "") if err != nil { c.Fatalf("%s: %s", instanceType, err) } @@ -621,7 +621,7 @@ func testListBuckets(obj ObjectLayer, instanceType string, c TestErrHandler) { } // add three and test exists + prefix. - err = obj.MakeBucket("bucket22") + err = obj.MakeBucketWithLocation("bucket22", "") if err != nil { c.Fatalf("%s: %s", instanceType, err) } @@ -645,11 +645,11 @@ func testListBucketsOrder(obj ObjectLayer, instanceType string, c TestErrHandler // if implementation contains a map, order of map keys will vary. // this ensures they return in the same order each time. // add one and test exists. - err := obj.MakeBucket("bucket1") + err := obj.MakeBucketWithLocation("bucket1", "") if err != nil { c.Fatalf("%s: %s", instanceType, err) } - err = obj.MakeBucket("bucket2") + err = obj.MakeBucketWithLocation("bucket2", "") if err != nil { c.Fatalf("%s: %s", instanceType, err) } @@ -698,7 +698,7 @@ func (s *ObjectLayerAPISuite) TestNonExistantObjectInBucket(c *C) { // Tests validate that GetObject fails on a non-existent bucket as expected. func testNonExistantObjectInBucket(obj ObjectLayer, instanceType string, c TestErrHandler) { - err := obj.MakeBucket("bucket") + err := obj.MakeBucketWithLocation("bucket", "") if err != nil { c.Fatalf("%s: %s", instanceType, err) } @@ -736,7 +736,7 @@ 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) { bucketName := "bucket" - err := obj.MakeBucket(bucketName) + err := obj.MakeBucketWithLocation(bucketName, "") if err != nil { c.Fatalf("%s: %s", instanceType, err) } @@ -781,7 +781,7 @@ func (s *ObjectLayerAPISuite) TestContentType(c *C) { // Test content-type. func testContentType(obj ObjectLayer, instanceType string, c TestErrHandler) { - err := obj.MakeBucket("bucket") + err := obj.MakeBucketWithLocation("bucket", "") if err != nil { c.Fatalf("%s: %s", instanceType, err) } diff --git a/cmd/post-policy_test.go b/cmd/post-policy_test.go index 2f03ae2fa..c35d1503e 100644 --- a/cmd/post-policy_test.go +++ b/cmd/post-policy_test.go @@ -143,7 +143,7 @@ func testPostPolicyBucketHandler(obj ObjectLayer, instanceType string, t TestErr // objectNames[0]. // uploadIds [0]. // Create bucket before initiating NewMultipartUpload. - err = obj.MakeBucket(bucketName) + err = obj.MakeBucketWithLocation(bucketName, "") if err != nil { // Failed to create newbucket, abort. t.Fatalf("%s : %s", instanceType, err.Error()) @@ -459,7 +459,7 @@ func testPostPolicyBucketHandlerRedirect(obj ObjectLayer, instanceType string, t curTime := UTCNow() curTimePlus5Min := curTime.Add(time.Minute * 5) - err = obj.MakeBucket(bucketName) + err = obj.MakeBucketWithLocation(bucketName, "") if err != nil { // Failed to create newbucket, abort. t.Fatalf("%s : %s", instanceType, err.Error()) diff --git a/cmd/test-utils_test.go b/cmd/test-utils_test.go index 42654c138..b81148a20 100644 --- a/cmd/test-utils_test.go +++ b/cmd/test-utils_test.go @@ -1721,7 +1721,7 @@ func initAPIHandlerTest(obj ObjectLayer, endpoints []string) (string, http.Handl bucketName := getRandomBucketName() // Create bucket. - err := obj.MakeBucket(bucketName) + err := obj.MakeBucketWithLocation(bucketName, "") if err != nil { // failed to create newbucket, return err. return "", nil, err diff --git a/cmd/web-handlers.go b/cmd/web-handlers.go index 653e64872..bc4992e2e 100644 --- a/cmd/web-handlers.go +++ b/cmd/web-handlers.go @@ -132,7 +132,8 @@ func (web *webAPIHandlers) MakeBucket(r *http.Request, args *MakeBucketArgs, rep bucketLock := globalNSMutex.NewNSLock(args.BucketName, "") bucketLock.Lock() defer bucketLock.Unlock() - if err := objectAPI.MakeBucket(args.BucketName); err != nil { + + if err := objectAPI.MakeBucketWithLocation(args.BucketName, serverConfig.GetRegion()); err != nil { return toJSONError(err, args.BucketName) } @@ -679,6 +680,22 @@ func readBucketAccessPolicy(objAPI ObjectLayer, bucketName string) (policy.Bucke } +func getBucketAccessPolicy(objAPI ObjectLayer, bucketName string) (policy.BucketAccessPolicy, error) { + // FIXME: remove this code when S3 layer for gateway and server is unified. + var policyInfo policy.BucketAccessPolicy + var err error + + switch layer := objAPI.(type) { + case *s3Objects: + policyInfo, err = layer.GetBucketPolicies(bucketName) + case *azureObjects: + policyInfo, err = layer.GetBucketPolicies(bucketName) + default: + policyInfo, err = readBucketAccessPolicy(objAPI, bucketName) + } + return policyInfo, err +} + // GetBucketPolicy - get bucket policy. func (web *webAPIHandlers) GetBucketPolicy(r *http.Request, args *GetBucketPolicyArgs, reply *GetBucketPolicyRep) error { objectAPI := web.ObjectAPI() @@ -728,12 +745,14 @@ func (web *webAPIHandlers) ListAllBucketPolicies(r *http.Request, args *ListAllB if !isHTTPRequestValid(r) { return toJSONError(errAuthentication) } + var policyInfo, err = getBucketAccessPolicy(objectAPI, args.BucketName) - policyInfo, err := readBucketAccessPolicy(objectAPI, args.BucketName) if err != nil { - return toJSONError(err, args.BucketName) + _, ok := errorCause(err).(PolicyNotFound) + if !ok { + return toJSONError(err, args.BucketName) + } } - reply.UIVersion = browser.UIVersion for prefix, policy := range policy.GetPolicies(policyInfo.Statements, args.BucketName) { reply.Policies = append(reply.Policies, BucketAccessPolicy{ @@ -769,11 +788,26 @@ func (web *webAPIHandlers) SetBucketPolicy(r *http.Request, args *SetBucketPolic } } - policyInfo, err := readBucketAccessPolicy(objectAPI, args.BucketName) + var policyInfo, err = getBucketAccessPolicy(objectAPI, args.BucketName) + if err != nil { - return toJSONError(err, args.BucketName) + if _, ok := errorCause(err).(PolicyNotFound); !ok { + return toJSONError(err, args.BucketName) + } + policyInfo = policy.BucketAccessPolicy{Version: "2012-10-17"} } + policyInfo.Statements = policy.SetPolicy(policyInfo.Statements, bucketP, args.BucketName, args.Prefix) + switch g := objectAPI.(type) { + case GatewayLayer: + err = g.SetBucketPolicies(args.BucketName, policyInfo) + if err != nil { + return toJSONError(err) + } + reply.UIVersion = browser.UIVersion + return nil + } + if len(policyInfo.Statements) == 0 { err = persistAndNotifyBucketPolicyChange(args.BucketName, policyChange{true, nil}, objectAPI) if err != nil { @@ -798,7 +832,6 @@ func (web *webAPIHandlers) SetBucketPolicy(r *http.Request, args *SetBucketPolic } return toJSONError(err, args.BucketName) } - reply.UIVersion = browser.UIVersion return nil } diff --git a/cmd/web-handlers_test.go b/cmd/web-handlers_test.go index 64488268b..13681b7e7 100644 --- a/cmd/web-handlers_test.go +++ b/cmd/web-handlers_test.go @@ -315,7 +315,7 @@ func testListBucketsWebHandler(obj ObjectLayer, instanceType string, t TestErrHa bucketName := getRandomBucketName() // Create bucket. - err = obj.MakeBucket(bucketName) + err = obj.MakeBucketWithLocation(bucketName, "") if err != nil { // failed to create newbucket, abort. t.Fatalf("%s : %s", instanceType, err) @@ -366,7 +366,7 @@ func testListObjectsWebHandler(obj ObjectLayer, instanceType string, t TestErrHa objectSize := 1 * humanize.KiByte // Create bucket. - err = obj.MakeBucket(bucketName) + err = obj.MakeBucketWithLocation(bucketName, "") if err != nil { // failed to create newbucket, abort. t.Fatalf("%s : %s", instanceType, err) @@ -460,7 +460,7 @@ func testRemoveObjectWebHandler(obj ObjectLayer, instanceType string, t TestErrH objectSize := 1 * humanize.KiByte // Create bucket. - err = obj.MakeBucket(bucketName) + err = obj.MakeBucketWithLocation(bucketName, "") if err != nil { // failed to create newbucket, abort. t.Fatalf("%s : %s", instanceType, err) @@ -700,7 +700,7 @@ func testUploadWebHandler(obj ObjectLayer, instanceType string, t TestErrHandler return rec.Code } // Create bucket. - err = obj.MakeBucket(bucketName) + err = obj.MakeBucketWithLocation(bucketName, "") if err != nil { // failed to create newbucket, abort. t.Fatalf("%s : %s", instanceType, err) @@ -785,7 +785,7 @@ func testDownloadWebHandler(obj ObjectLayer, instanceType string, t TestErrHandl } // Create bucket. - err = obj.MakeBucket(bucketName) + err = obj.MakeBucketWithLocation(bucketName, "") if err != nil { // failed to create newbucket, abort. t.Fatalf("%s : %s", instanceType, err) @@ -852,7 +852,7 @@ func testWebHandlerDownloadZip(obj ObjectLayer, instanceType string, t TestErrHa fileThree := "cccccccccccccc" // Create bucket. - err = obj.MakeBucket(bucket) + err = obj.MakeBucketWithLocation(bucket, "") if err != nil { // failed to create newbucket, abort. t.Fatalf("%s : %s", instanceType, err) @@ -937,7 +937,7 @@ func testWebPresignedGetHandler(obj ObjectLayer, instanceType string, t TestErrH objectSize := 1 * humanize.KiByte // Create bucket. - err = obj.MakeBucket(bucketName) + err = obj.MakeBucketWithLocation(bucketName, "") if err != nil { // failed to create newbucket, abort. t.Fatalf("%s : %s", instanceType, err) @@ -1037,7 +1037,7 @@ func testWebGetBucketPolicyHandler(obj ObjectLayer, instanceType string, t TestE rec := httptest.NewRecorder() bucketName := getRandomBucketName() - if err := obj.MakeBucket(bucketName); err != nil { + if err := obj.MakeBucketWithLocation(bucketName, ""); err != nil { t.Fatal("Unexpected error: ", err) } @@ -1111,7 +1111,7 @@ func testWebListAllBucketPoliciesHandler(obj ObjectLayer, instanceType string, t rec := httptest.NewRecorder() bucketName := getRandomBucketName() - if err := obj.MakeBucket(bucketName); err != nil { + if err := obj.MakeBucketWithLocation(bucketName, ""); err != nil { t.Fatal("Unexpected error: ", err) } @@ -1209,7 +1209,7 @@ func testWebSetBucketPolicyHandler(obj ObjectLayer, instanceType string, t TestE // Create a bucket bucketName := getRandomBucketName() - if err = obj.MakeBucket(bucketName); err != nil { + if err = obj.MakeBucketWithLocation(bucketName, ""); err != nil { t.Fatal("Unexpected error: ", err) } @@ -1445,7 +1445,7 @@ func TestWebObjectLayerFaultyDisks(t *testing.T) { defer removeRoots(fsDirs) bucketName := "mybucket" - err = obj.MakeBucket(bucketName) + err = obj.MakeBucketWithLocation(bucketName, "") if err != nil { t.Fatal("Cannot make bucket:", err) } diff --git a/cmd/xl-v1-bucket.go b/cmd/xl-v1-bucket.go index af1eaca30..54dee3a6d 100644 --- a/cmd/xl-v1-bucket.go +++ b/cmd/xl-v1-bucket.go @@ -30,7 +30,7 @@ var bucketMetadataOpIgnoredErrs = append(bucketOpIgnoredErrs, errVolumeNotFound) /// Bucket operations // MakeBucket - make a bucket. -func (xl xlObjects) MakeBucket(bucket string) error { +func (xl xlObjects) MakeBucketWithLocation(bucket, location string) error { // Verify if bucket is valid. if !IsValidBucketName(bucket) { return traceError(BucketNameInvalid{Bucket: bucket}) diff --git a/cmd/xl-v1-healing-common_test.go b/cmd/xl-v1-healing-common_test.go index bceb35dfb..d9705395c 100644 --- a/cmd/xl-v1-healing-common_test.go +++ b/cmd/xl-v1-healing-common_test.go @@ -214,7 +214,7 @@ func TestListOnlineDisks(t *testing.T) { obj.DeleteObject(bucket, object) obj.DeleteBucket(bucket) - err = obj.MakeBucket("bucket") + err = obj.MakeBucketWithLocation("bucket", "") if err != nil { t.Fatalf("Failed to make a bucket %v", err) } diff --git a/cmd/xl-v1-healing_test.go b/cmd/xl-v1-healing_test.go index c230281bd..82853c17e 100644 --- a/cmd/xl-v1-healing_test.go +++ b/cmd/xl-v1-healing_test.go @@ -212,7 +212,7 @@ func TestHealFormatXL(t *testing.T) { t.Fatal(err) } xl = obj.(*xlObjects) - if err = obj.MakeBucket(getRandomBucketName()); err != nil { + if err = obj.MakeBucketWithLocation(getRandomBucketName(), ""); err != nil { t.Fatal(err) } for i := 0; i <= 2; i++ { @@ -248,7 +248,7 @@ func TestUndoMakeBucket(t *testing.T) { } bucketName := getRandomBucketName() - if err = obj.MakeBucket(bucketName); err != nil { + if err = obj.MakeBucketWithLocation(bucketName, ""); err != nil { t.Fatal(err) } xl := obj.(*xlObjects) @@ -288,7 +288,7 @@ func TestQuickHeal(t *testing.T) { } bucketName := getRandomBucketName() - if err = obj.MakeBucket(bucketName); err != nil { + if err = obj.MakeBucketWithLocation(bucketName, ""); err != nil { t.Fatal(err) } @@ -383,13 +383,13 @@ func TestListBucketsHeal(t *testing.T) { // Create a bucket that won't get corrupted saneBucket := "sanebucket" - if err = obj.MakeBucket(saneBucket); err != nil { + if err = obj.MakeBucketWithLocation(saneBucket, ""); err != nil { t.Fatal(err) } // Create a bucket that will be removed in some disks corruptedBucketName := getRandomBucketName() - if err = obj.MakeBucket(corruptedBucketName); err != nil { + if err = obj.MakeBucketWithLocation(corruptedBucketName, ""); err != nil { t.Fatal(err) } @@ -445,7 +445,7 @@ func TestHealObjectXL(t *testing.T) { object := "object" data := bytes.Repeat([]byte("a"), 5*1024*1024) - err = obj.MakeBucket(bucket) + err = obj.MakeBucketWithLocation(bucket, "") if err != nil { t.Fatalf("Failed to make a bucket - %v", err) } diff --git a/cmd/xl-v1-list-objects-heal_test.go b/cmd/xl-v1-list-objects-heal_test.go index af12060b2..4782aa353 100644 --- a/cmd/xl-v1-list-objects-heal_test.go +++ b/cmd/xl-v1-list-objects-heal_test.go @@ -48,7 +48,7 @@ func TestListObjectsHeal(t *testing.T) { objName := "obj" // Create test bucket - err = xl.MakeBucket(bucketName) + err = xl.MakeBucketWithLocation(bucketName, "") if err != nil { t.Fatal(err) } @@ -166,7 +166,7 @@ func TestListUploadsHeal(t *testing.T) { objName := path.Join(prefix, "obj") // Create test bucket. - err = xl.MakeBucket(bucketName) + err = xl.MakeBucketWithLocation(bucketName, "") if err != nil { t.Fatal(err) } diff --git a/cmd/xl-v1-metadata_test.go b/cmd/xl-v1-metadata_test.go index c20339e95..028d116ea 100644 --- a/cmd/xl-v1-metadata_test.go +++ b/cmd/xl-v1-metadata_test.go @@ -37,7 +37,7 @@ func testXLReadStat(obj ObjectLayer, instanceType string, disks []string, t *tes bucketName := getRandomBucketName() objectName := "test-object" // create bucket. - err := obj.MakeBucket(bucketName) + err := obj.MakeBucketWithLocation(bucketName, "") // Stop the test if creation of the bucket fails. if err != nil { t.Fatalf("%s : %s", instanceType, err.Error()) @@ -113,7 +113,7 @@ func testXLReadMetaParts(obj ObjectLayer, instanceType string, disks []string, t // objectNames[0]. // uploadIds [0]. // Create bucket before intiating NewMultipartUpload. - err := obj.MakeBucket(bucketNames[0]) + err := obj.MakeBucketWithLocation(bucketNames[0] ,"") if err != nil { // Failed to create newbucket, abort. t.Fatalf("%s : %s", instanceType, err.Error()) diff --git a/cmd/xl-v1-multipart_test.go b/cmd/xl-v1-multipart_test.go index 71d5dd03b..63f735a02 100644 --- a/cmd/xl-v1-multipart_test.go +++ b/cmd/xl-v1-multipart_test.go @@ -38,7 +38,7 @@ func TestUpdateUploadJSON(t *testing.T) { defer removeRoots(fsDirs) bucket, object := "bucket", "object" - err = obj.MakeBucket(bucket) + err = obj.MakeBucketWithLocation(bucket, "") if err != nil { t.Fatal(err) } diff --git a/cmd/xl-v1-object_test.go b/cmd/xl-v1-object_test.go index 86ffd8648..6e9eba874 100644 --- a/cmd/xl-v1-object_test.go +++ b/cmd/xl-v1-object_test.go @@ -42,7 +42,7 @@ func TestRepeatPutObjectPart(t *testing.T) { // cleaning up of temporary test directories defer removeRoots(disks) - err = objLayer.MakeBucket("bucket1") + err = objLayer.MakeBucketWithLocation("bucket1", "") if err != nil { t.Fatal(err) } @@ -86,7 +86,7 @@ func TestXLDeleteObjectBasic(t *testing.T) { } // Make bucket for Test 7 to pass - err = xl.MakeBucket("bucket") + err = xl.MakeBucketWithLocation("bucket", "") if err != nil { t.Fatal(err) } @@ -120,7 +120,7 @@ func TestXLDeleteObjectDiskNotFound(t *testing.T) { xl := obj.(*xlObjects) // Create "bucket" - err = obj.MakeBucket("bucket") + err = obj.MakeBucketWithLocation("bucket", "") if err != nil { t.Fatal(err) } @@ -170,7 +170,7 @@ func TestGetObjectNoQuorum(t *testing.T) { xl := obj.(*xlObjects) // Create "bucket" - err = obj.MakeBucket("bucket") + err = obj.MakeBucketWithLocation("bucket", "") if err != nil { t.Fatal(err) } @@ -222,7 +222,7 @@ func TestPutObjectNoQuorum(t *testing.T) { xl := obj.(*xlObjects) // Create "bucket" - err = obj.MakeBucket("bucket") + err = obj.MakeBucketWithLocation("bucket", "") if err != nil { t.Fatal(err) } @@ -279,7 +279,7 @@ func TestHealing(t *testing.T) { xl := obj.(*xlObjects) // Create "bucket" - err = obj.MakeBucket("bucket") + err = obj.MakeBucketWithLocation("bucket", "") if err != nil { t.Fatal(err) } diff --git a/docs/gateway/README.md b/docs/gateway/README.md index f3459bb3f..00922babf 100644 --- a/docs/gateway/README.md +++ b/docs/gateway/README.md @@ -16,7 +16,10 @@ export MINIO_ACCESS_KEY=azureaccountname export MINIO_SECRET_KEY=azureaccountkey minio gateway azure ``` +## Test using Minio Browser +Minio Gateway comes with an embedded web based object browser. Point your web browser to http://127.0.0.1:9000 ensure your server has started successfully. +![Screenshot](https://github.com/minio/minio/blob/master/docs/screenshots/minio-browser.jpg?raw=true) ## 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. From 432bf7d99e5c24c074a1c2bac0f2fadc9891e0a0 Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Fri, 2 Jun 2017 14:05:51 -0700 Subject: [PATCH 54/80] Fail if formatting is wrong in our CI tests. (#4459) We didn't fail before, we should helps in avoiding formatting issues to creep into the codebase. --- .travis.yml | 2 ++ Makefile | 62 +++++++++++++++----------------------- cmd/admin-handlers_test.go | 10 +++--- cmd/gateway-main.go | 5 +-- cmd/storage-rpc-client.go | 2 +- cmd/xl-v1-metadata_test.go | 2 +- 6 files changed, 37 insertions(+), 46 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2052a4fd6..e37f4da01 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,6 +18,8 @@ env: script: ## Run all the tests - make +- diff -au <(gofmt -d cmd) <(printf "") +- diff -au <(gofmt -d pkg) <(printf "") - make test GOFLAGS="-timeout 15m -race -v" - make coverage diff --git a/Makefile b/Makefile index 4db4954ba..27a10392f 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,7 @@ LDFLAGS := $(shell go run buildscripts/gen-ldflags.go) PWD := $(shell pwd) GOPATH := $(shell go env GOPATH) + BUILD_LDFLAGS := '$(LDFLAGS)' TAG := latest @@ -56,55 +57,43 @@ endif all: install checks: - @echo -n "Check deps: " + @echo "Check deps" @(env bash $(PWD)/buildscripts/checkdeps.sh) - @echo "Done." - @echo -n "Checking project is in GOPATH: " + @echo "Checking project is in GOPATH" @(env bash $(PWD)/buildscripts/checkgopath.sh) - @echo "Done." getdeps: checks - @echo -n "Installing golint: " && go get -u github.com/golang/lint/golint - @echo "Done." - @echo -n "Installing gocyclo: " && go get -u github.com/fzipp/gocyclo - @echo "Done." - @echo -n "Installing deadcode: " && go get -u github.com/remyoudompheng/go-misc/deadcode - @echo "Done." - @echo -n "Installing misspell: " && go get -u github.com/client9/misspell/cmd/misspell - @echo "Done." - @echo -n "Installing ineffassign: " && go get -u github.com/gordonklaus/ineffassign - @echo "Done." + @echo "Installing golint" && go get -u github.com/golang/lint/golint + @echo "Installing gocyclo" && go get -u github.com/fzipp/gocyclo + @echo "Installing deadcode" && go get -u github.com/remyoudompheng/go-misc/deadcode + @echo "Installing misspell" && go get -u github.com/client9/misspell/cmd/misspell + @echo "Installing ineffassign" && go get -u github.com/gordonklaus/ineffassign verifiers: vet fmt lint cyclo spelling vet: - @echo -n "Running $@: " + @echo "Running $@" @go tool vet -atomic -bool -copylocks -nilfunc -printf -shadow -rangeloops -unreachable -unsafeptr -unusedresult cmd @go tool vet -atomic -bool -copylocks -nilfunc -printf -shadow -rangeloops -unreachable -unsafeptr -unusedresult pkg - @echo "Done." fmt: - @echo -n "Running $@: " - @gofmt -s -l cmd - @gofmt -s -l pkg - @echo "Done." + @echo "Running $@" + @gofmt -d cmd + @gofmt -d pkg lint: - @echo -n "Running $@: " + @echo "Running $@" @${GOPATH}/bin/golint -set_exit_status github.com/minio/minio/cmd... @${GOPATH}/bin/golint -set_exit_status github.com/minio/minio/pkg... - @echo "Done." ineffassign: - @echo -n "Running $@: " + @echo "Running $@" @${GOPATH}/bin/ineffassign . - @echo "Done." cyclo: - @echo -n "Running $@: " + @echo "Running $@" @${GOPATH}/bin/gocyclo -over 100 cmd @${GOPATH}/bin/gocyclo -over 100 pkg - @echo "Done." build: getdeps verifiers $(UI_ASSETS) @@ -117,30 +106,30 @@ spelling: @${GOPATH}/bin/misspell -error `find docs/` test: build - @echo -n "Running all minio testing: " + @echo "Running all minio testing" @go test $(GOFLAGS) . @go test $(GOFLAGS) github.com/minio/minio/cmd... @go test $(GOFLAGS) github.com/minio/minio/pkg... - @echo "Done." coverage: build - @echo "Running all coverage for minio: " + @echo "Running all coverage for minio" @./buildscripts/go-coverage.sh - @echo "Done." gomake-all: build - @echo -n "Installing minio at $(GOPATH)/bin/minio: " + @echo "Installing minio at $(GOPATH)/bin/minio" @go build --ldflags $(BUILD_LDFLAGS) -o $(GOPATH)/bin/minio - @echo "Done." pkg-add: - ${GOPATH}/bin/govendor add $(PKG) + @echo "Adding new package $(PKG)" + @${GOPATH}/bin/govendor add $(PKG) pkg-update: - ${GOPATH}/bin/govendor update $(PKG) + @echo "Updating new package $(PKG)" + @${GOPATH}/bin/govendor update $(PKG) pkg-remove: - ${GOPATH}/bin/govendor remove $(PKG) + @echo "Remove new package $(PKG)" + @${GOPATH}/bin/govendor remove $(PKG) pkg-list: @$(GOPATH)/bin/govendor list @@ -154,8 +143,7 @@ experimental: verifiers @MINIO_RELEASE=EXPERIMENTAL ./buildscripts/build.sh clean: - @echo -n "Cleaning up all the generated files: " + @echo "Cleaning up all the generated files" @find . -name '*.test' | xargs rm -fv @rm -rf build @rm -rf release - @echo "Done." diff --git a/cmd/admin-handlers_test.go b/cmd/admin-handlers_test.go index deba24f5b..f737a1a6f 100644 --- a/cmd/admin-handlers_test.go +++ b/cmd/admin-handlers_test.go @@ -731,7 +731,7 @@ func TestListObjectsHealHandler(t *testing.T) { } defer adminTestBed.TearDown() - err = adminTestBed.objLayer.MakeBucketWithLocation("mybucket","") + err = adminTestBed.objLayer.MakeBucketWithLocation("mybucket", "") if err != nil { t.Fatalf("Failed to make bucket - %v", err) } @@ -859,7 +859,7 @@ func TestHealBucketHandler(t *testing.T) { } defer adminTestBed.TearDown() - err = adminTestBed.objLayer.MakeBucketWithLocation("mybucket","") + err = adminTestBed.objLayer.MakeBucketWithLocation("mybucket", "") if err != nil { t.Fatalf("Failed to make bucket - %v", err) } @@ -936,7 +936,7 @@ func TestHealObjectHandler(t *testing.T) { // Create an object myobject under bucket mybucket. bucketName := "mybucket" objName := "myobject" - err = adminTestBed.objLayer.MakeBucketWithLocation(bucketName,"") + err = adminTestBed.objLayer.MakeBucketWithLocation(bucketName, "") if err != nil { t.Fatalf("Failed to make bucket %s - %v", bucketName, err) } @@ -1067,7 +1067,7 @@ func TestHealUploadHandler(t *testing.T) { // Create an object myobject under bucket mybucket. bucketName := "mybucket" objName := "myobject" - err = adminTestBed.objLayer.MakeBucketWithLocation(bucketName,"") + err = adminTestBed.objLayer.MakeBucketWithLocation(bucketName, "") if err != nil { t.Fatalf("Failed to make bucket %s - %v", bucketName, err) } @@ -1455,7 +1455,7 @@ func TestListHealUploadsHandler(t *testing.T) { } defer adminTestBed.TearDown() - err = adminTestBed.objLayer.MakeBucketWithLocation("mybucket","") + err = adminTestBed.objLayer.MakeBucketWithLocation("mybucket", "") if err != nil { t.Fatalf("Failed to make bucket - %v", err) } diff --git a/cmd/gateway-main.go b/cmd/gateway-main.go index cd48525b6..254d02ec9 100644 --- a/cmd/gateway-main.go +++ b/cmd/gateway-main.go @@ -99,7 +99,7 @@ func mustGetGatewayCredsFromEnv() (accessKey, secretKey string) { } // Set browser setting from environment variables -func mustSetBrowserSettingFromEnv(){ +func mustSetBrowserSettingFromEnv() { if browser := os.Getenv("MINIO_BROWSER"); browser != "" { browserFlag, err := ParseBrowserFlag(browser) if err != nil { @@ -112,6 +112,7 @@ func mustSetBrowserSettingFromEnv(){ globalIsBrowserEnabled = bool(browserFlag) } } + // Initialize gateway layer depending on the backend type. // Supported backend types are // @@ -192,7 +193,7 @@ func gatewayMain(ctx *cli.Context) { mustSetBrowserSettingFromEnv() // Initialize new gateway config. - + newGatewayConfig(accessKey, secretKey, globalMinioDefaultRegion) // Get quiet flag from command line argument. diff --git a/cmd/storage-rpc-client.go b/cmd/storage-rpc-client.go index ee31587b1..982e5e158 100644 --- a/cmd/storage-rpc-client.go +++ b/cmd/storage-rpc-client.go @@ -28,7 +28,7 @@ import ( ) type networkStorage struct { - rpcClient *AuthRPCClient + rpcClient *AuthRPCClient } const ( diff --git a/cmd/xl-v1-metadata_test.go b/cmd/xl-v1-metadata_test.go index 028d116ea..fa7f35543 100644 --- a/cmd/xl-v1-metadata_test.go +++ b/cmd/xl-v1-metadata_test.go @@ -113,7 +113,7 @@ func testXLReadMetaParts(obj ObjectLayer, instanceType string, disks []string, t // objectNames[0]. // uploadIds [0]. // Create bucket before intiating NewMultipartUpload. - err := obj.MakeBucketWithLocation(bucketNames[0] ,"") + err := obj.MakeBucketWithLocation(bucketNames[0], "") if err != nil { // Failed to create newbucket, abort. t.Fatalf("%s : %s", instanceType, err.Error()) From a4d1ef1b62d2921a1711c189e48dc28700fbd50e Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Fri, 2 Jun 2017 15:11:47 -0700 Subject: [PATCH 55/80] browser: update ui-assets with new changes. (#4467) Fixes #4269 --- browser/ui-assets.go | 60 ++++++++++++++++++++++---------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/browser/ui-assets.go b/browser/ui-assets.go index 202b5f99e..30e69f27c 100644 --- a/browser/ui-assets.go +++ b/browser/ui-assets.go @@ -4,7 +4,7 @@ // production/favicon.ico // production/firefox.png // production/index.html -// production/index_bundle-2017-05-04T20-58-52Z.js +// production/index_bundle-2017-06-02T21-36-18Z.js // production/loader.css // production/logo.svg // production/safari.png @@ -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(1493931552, 0)} + info := bindataFileInfo{name: "production/chrome.png", size: 3726, mode: os.FileMode(436), modTime: time.Unix(1496439395, 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(1493931552, 0)} + info := bindataFileInfo{name: "production/favicon.ico", size: 1340, mode: os.FileMode(436), modTime: time.Unix(1496439395, 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(1493931552, 0)} + info := bindataFileInfo{name: "production/firefox.png", size: 4795, mode: os.FileMode(436), modTime: time.Unix(1496439395, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -156,8 +156,8 @@ var _productionIndexHTML = []byte(` - - + + `) @@ -172,22 +172,22 @@ func productionIndexHTML() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "production/index.html", size: 1996, mode: os.FileMode(436), modTime: time.Unix(1493931552, 0)} + info := bindataFileInfo{name: "production/index.html", size: 1996, mode: os.FileMode(436), modTime: time.Unix(1496439395, 0)} a := &asset{bytes: bytes, info: info} return a, nil } -var _productionIndex_bundle20170504t205852zJs = []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(349)},function(A,M,t){var I=t(5),g=t(48),e=t(25),i=t(26),T=t(49),E="prototype",N=function(A,M,t){var n,o,c,C,a=A&N.F,D=A&N.G,r=A&N.S,B=A&N.P,Q=A&N.B,s=D?I:r?I[M]||(I[M]={}):(I[M]||{})[E],u=D?g:g[M]||(g[M]={}),x=u[E]||(u[E]={});D&&(t=M);for(n in t)o=!a&&s&&void 0!==s[n],c=(o?s:t)[n],C=Q&&o?T(c,I):B&&"function"==typeof c?T(Function.call,c):c,s&&i(s,n,c,A&N.U),u[n]!=c&&e(u,n,C),B&&x[n]!=c&&(x[n]=c)};I.core=g,N.F=1,N.G=2,N.S=4,N.P=8,N.B=16,N.W=32,N.U=64,N.R=128,A.exports=N},function(A,M,t){"use strict";A.exports=t(732)},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],n=0;E=new Error(M.replace(/%s/g,function(){return N[n++]})),E.name="Invariant Violation"}throw E.framesToPop=1,E}}A.exports=I},function(A,M,t){var I=t(9);A.exports=function(A){if(!I(A))throw TypeError(A+" is not an object!");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){A.exports=function(A){try{return!!A()}catch(A){return!0}}},function(A,M,t){"use strict";var I=t(44),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;g0?g(I(A),9007199254740991):0}},function(A,M,t){"use strict";function I(A){return A&&A.__esModule?A:{default:A}}function g(A,M){if(M.indexOf("deprecated")!==-1){if(E[M])return;E[M]=!0}M="[react-router] "+M;for(var t=arguments.length,I=Array(t>2?t-2:0),g=2;g"+g+""};A.exports=function(A,M){var t={};t[A]=M(T),I(I.P+I.F*g(function(){var M=""[A]('"');return M!==M.toLowerCase()||M.split('"').length>3}),"String",t)}},function(A,M,t){var I=t(95),g=t(36);A.exports=function(A){return I(g(A))}},function(A,M,t){"use strict";var I=t(53),g=t(8),e=(t(133),"function"==typeof Symbol&&Symbol.for&&Symbol.for("react.element")||60103),i={key:!0,ref:!0,__self:!0,__source:!0},T=function(A,M,t,I,g,i,T){var E={$$typeof:e,type:A,key:M,ref:t,props:T,_owner:i};return E};T.createElement=function(A,M,t){var g,e={},E=null,N=null,n=null,o=null;if(null!=M){N=void 0===M.ref?null:M.ref,E=void 0===M.key?null:""+M.key,n=void 0===M.__self?null:M.__self,o=void 0===M.__source?null:M.__source;for(g in M)M.hasOwnProperty(g)&&!i.hasOwnProperty(g)&&(e[g]=M[g])}var c=arguments.length-2;if(1===c)e.children=t;else if(c>1){for(var C=Array(c),a=0;a1){for(var D=Array(a),r=0;rx;x++)if((c||x in Q)&&(D=Q[x],r=s(D,x,B),A))if(t)y[x]=r;else if(r)switch(A){case 3:return!0;case 5:return D;case 6:return x;case 2:y.push(D)}else if(n)return!1;return o?-1:N||n?n:y}}},function(A,M,t){var I=t(1),g=t(48),e=t(6);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(9);A.exports=function(A,M){if(!I(A))return A;var t,g;if(M&&"function"==typeof(t=A.toString)&&!I(g=t.call(A)))return g;if("function"==typeof(t=A.valueOf)&&!I(g=t.call(A)))return g;if(!M&&"function"==typeof(t=A.toString)&&!I(g=t.call(A)))return g;throw TypeError("Can't convert object to primitive value")}},function(A,M){"use strict";function t(A){return function(){return A}}function I(){}I.thatReturns=t,I.thatReturnsFalse=t(!1),I.thatReturnsTrue=t(!0),I.thatReturnsNull=t(null),I.thatReturnsThis=function(){return this},I.thatReturnsArgument=function(A){return A},A.exports=I},function(A,M,t){function I(A){if(i.unindexedChars&&e(A)){for(var M=-1,t=A.length,I=Object(A);++M3&&void 0!==arguments[3]?arguments[3]:{},N=Boolean(A),c=A||l,a=void 0;a="function"==typeof M?M:M?(0,B.default)(M):w;var r=t||L,Q=I.pure,s=void 0===Q||Q,u=I.withRef,y=void 0!==u&&u,h=s&&r!==L,S=d++;return function(A){function M(A,M,t){var I=r(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,j.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=a(A.dispatch,M),I="function"==typeof t;return this.finalMapDispatchToProps=I?t:a,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,D.default)(A,this.stateProps))&&(this.stateProps=A,!0)},T.prototype.updateDispatchPropsIfNeeded=function(){var A=this.computeDispatchProps(this.store,this.props);return(!this.dispatchProps||!(0,D.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,D.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,D.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,j.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,o.createElement)(A,n({},this.mergedProps,{ref:"wrappedInstance"})):this.renderedElement=(0,o.createElement)(A,this.mergedProps),this.renderedElement)},T}(o.Component);return I.displayName=t,I.WrappedComponent=A,I.contextTypes={store:C.default},I.propTypes={store:C.default},(0,x.default)(I,A)}}M.__esModule=!0;var n=Object.assign||function(A){ -for(var M=1;Mt;)g[t]=M[t++];return g},fA=function(A,M,t){G(A,M,{get:function(){return this._d[t]}})},mA=function(A){var M,t,I,g,e,i,T=y(A),E=arguments.length,n=E>1?arguments[1]:void 0,o=void 0!==n,c=Y(T);if(void 0!=c&&!j(c)){for(i=c.call(T),I=[],M=0;!(e=i.next()).done;M++)I.push(e.value);T=I}for(o&&E>2&&(n=N(n,arguments[2],2)),M=0,t=D(T.length),g=pA(this,t);t>M;M++)g[M]=o?n(T[M],M):T[M];return g},FA=function(){for(var A=0,M=arguments.length,t=pA(this,M);M>A;)t[A]=arguments[A++];return t},kA=!!X&&e(function(){BA.call(new X(1))}),RA=function(){return BA.apply(kA?DA.call(zA(this)):zA(this),arguments)},JA={copyWithin:function(A,M){return k.call(zA(this),A,M,arguments.length>2?arguments[2]:void 0)},every:function(A){return tA(zA(this),A,arguments.length>1?arguments[1]:void 0)},fill:function(A){return F.apply(zA(this),arguments)},filter:function(A){return UA(this,AA(zA(this),A,arguments.length>1?arguments[1]:void 0))},find:function(A){return IA(zA(this),A,arguments.length>1?arguments[1]:void 0)},findIndex:function(A){return gA(zA(this),A,arguments.length>1?arguments[1]:void 0)},forEach:function(A){$(zA(this),A,arguments.length>1?arguments[1]:void 0)},indexOf:function(A){return iA(zA(this),A,arguments.length>1?arguments[1]:void 0)},includes:function(A){return eA(zA(this),A,arguments.length>1?arguments[1]:void 0)},join:function(A){return CA.apply(zA(this),arguments)},lastIndexOf:function(A){return nA.apply(zA(this),arguments)},map:function(A){return LA(zA(this),A,arguments.length>1?arguments[1]:void 0)},reduce:function(A){return oA.apply(zA(this),arguments)},reduceRight:function(A){return cA.apply(zA(this),arguments)},reverse:function(){for(var A,M=this,t=zA(M).length,I=Math.floor(t/2),g=0;g1?arguments[1]:void 0)},sort:function(A){return aA.call(zA(this),A)},subarray:function(A,M){var t=zA(this),I=t.length,g=r(A,I);return new(p(t,t[xA]))(t.buffer,t.byteOffset+g*t.BYTES_PER_ELEMENT,D((void 0===M?I:r(M,I))-g))}},GA=function(A,M){return UA(this,DA.call(zA(this),A,M))},HA=function(A){zA(this);var M=SA(arguments[1],1),t=this.length,I=y(A),g=D(I.length),e=0;if(g+M>t)throw v(wA);for(;e255?255:255&I),g.v[a](t*M+g.o,I,YA)},h=function(A,M){G(A,M,{get:function(){return Y(this,M)},set:function(A){return d(this,M,A)},enumerable:!0})};s?(r=t(function(A,t,I,g){n(A,r,N,"_d");var e,i,T,E,o=0,C=0;if(x(t)){if(!(t instanceof q||(E=u(t))==W||E==V))return jA in t?OA(r,t):mA.call(r,t);e=t,C=SA(I,M);var a=t.byteLength;if(void 0===g){if(a%M)throw v(wA);if(i=a-C,i<0)throw v(wA)}else if(i=D(g)*M,i+C>a)throw v(wA);T=i/M}else T=hA(t,!0),i=T*M,e=new q(i);for(c(A,"_d",{b:e,o:C,l:i,e:T,v:new _(e)});o0?I:t)(A)}},function(A,M){"use strict";var t=function(A){var M;for(M in A)if(A.hasOwnProperty(M))return M;return null};A.exports=t},function(A,M,t){var I=t(120),g=t(85),e=t(73),i="[object Array]",T=Object.prototype,E=T.toString,N=I(Array,"isArray"),n=N||function(A){return e(A)&&g(A.length)&&E.call(A)==i};A.exports=n},function(A,M){function t(A){var M=typeof A;return!!A&&("object"==M||"function"==M)}A.exports=t},function(A,M,t){"use strict";function I(A){return A&&A.__esModule?A:{default:A}}function g(A){return null==A||c.default.isValidElement(A)}function e(A){return g(A)||Array.isArray(A)&&A.every(g)}function i(A,M){return n({},A,M)}function T(A){var M=A.type,t=i(M.defaultProps,A.props);if(t.children){var I=E(t.children,t);I.length&&(t.childRoutes=I),delete t.children}return t}function E(A,M){var t=[];return c.default.Children.forEach(A,function(A){if(c.default.isValidElement(A))if(A.type.createRouteFromReactElement){var I=A.type.createRouteFromReactElement(A,M);I&&t.push(I)}else t.push(T(A))}),t}function N(A){return e(A)?A=E(A):A&&!Array.isArray(A)&&(A=[A]),A}M.__esModule=!0;var n=Object.assign||function(A){for(var M=1;M";for(M.style.display="none",t(142).appendChild(M),M.src="javascript:",A=M.contentWindow.document,A.open(),A.write(g+"script"+i+"document.F=Object"+g+"/script"+i),A.close(),N=A.F;I--;)delete N[E][e[I]];return N()};A.exports=Object.create||function(A,M){var t;return null!==A?(T[E]=I(A),t=new T,T[E]=null,t[i]=A):t=N(),void 0===M?t:g(t,M)}},function(A,M,t){var I=t(233),g=t(140).concat("length","prototype");M.f=Object.getOwnPropertyNames||function(A){return I(A,g)}},function(A,M,t){var I=t(233),g=t(140);A.exports=Object.keys||function(A){return I(A,g)}},function(A,M,t){var I=t(26);A.exports=function(A,M,t){for(var g in M)I(A,g,M[g],t);return A}},function(A,M,t){"use strict";var I=t(5),g=t(14),e=t(13),i=t(11)("species");A.exports=function(A){var M=I[A];e&&M&&!M[i]&&g.f(M,i,{configurable:!0,get:function(){return this}})}},function(A,M,t){var I=t(57),g=Math.max,e=Math.min;A.exports=function(A,M){return A=I(A),A<0?g(A+M,0):e(A,M)}},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){"use strict";A.exports=!("undefined"==typeof window||!window.document||!window.document.createElement)},function(A,M){function t(A){return!!A&&"object"==typeof A}A.exports=t},function(A,M,t){"use strict";function I(A,M,t){if(A[M])return new Error("<"+t+'> 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(2),e=g.PropTypes.func,i=g.PropTypes.object,T=g.PropTypes.arrayOf,E=g.PropTypes.oneOfType,N=g.PropTypes.element,n=g.PropTypes.shape,o=g.PropTypes.string,c=(M.history=n({listen:e.isRequired,push:e.isRequired,replace:e.isRequired,go:e.isRequired,goBack:e.isRequired,goForward:e.isRequired}),M.component=E([e,o])),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(47);I(i)},function(A,M,t){"use strict";function I(){g.attachRefs(this,this._currentElement)}var g=t(751),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(62),e=t(8),i=t(44),T=(t(7),{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,t){var I=t(11)("unscopables"),g=Array.prototype;void 0==g[I]&&t(25)(g,I,{}),A.exports=function(A){g[I][A]=!0}},function(A,M,t){var I=t(49),g=t(227),e=t(144),i=t(4),T=t(17),E=t(161),N={},n={},M=A.exports=function(A,M,t,o,c){var C,a,D,r,B=c?function(){return A}:E(A),Q=I(t,o,M?2:1),s=0;if("function"!=typeof B)throw TypeError(A+" is not iterable!");if(e(B)){for(C=T(A.length);C>s;s++)if(r=M?Q(i(a=A[s])[0],a[1]):Q(A[s]),r===N||r===n)return r}else for(D=B.call(A);!(a=D.next()).done;)if(r=g(D,Q,a.value,M),r===N||r===n)return r};M.BREAK=N,M.RETURN=n},function(A,M){A.exports={}},function(A,M,t){var I=t(14).f,g=t(21),e=t(11)("toStringTag");A.exports=function(A,M,t){A&&!g(A=t?A:A.prototype,e)&&I(A,e,{configurable:!0,value:M})}},function(A,M,t){var I=t(1),g=t(36),e=t(6),i=t(157),T="["+i+"]",E="​…",N=RegExp("^"+T+T+"*"),n=RegExp(T+T+"*$"),o=function(A,M,t){var g={},T=e(function(){return!!i[A]()||E[A]()!=E}),N=g[A]=T?M(c):i[A];t&&(g[t]=N),I(I.P+I.F*T,"String",g)},c=o.trim=function(A,M){return A=String(g(A)),1&M&&(A=A.replace(N,"")),2&M&&(A=A.replace(n,"")),A};A.exports=o},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(72),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 DI.apply(null,arguments)}function t(A){DI=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 QI)I=QI[t],g=M[I],r(g)||(A[I]=g);return A}function Q(A){B(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 Q||null!=A&&null!=A._isAMomentObject}function u(A){return A<0?Math.ceil(A)||0:Math.floor(A)}function x(A){var M=+A,t=0;return 0!==M&&isFinite(M)&&(t=u(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 m(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?(v(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 v(A,M,t){A.isValid()&&A._d["set"+(A._isUTC?"UTC":"")+M](t)}function b(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&&(fI[A]=g),M&&(fI[M[0]]=function(){return W(g.apply(this,arguments),M[1],M[2])}),t&&(fI[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]=x(A)}),t=0;t=0&&isFinite(T.getFullYear())&&T.setFullYear(A),T}function uA(A){var M=new Date(Date.UTC.apply(null,arguments));return A<100&&A>=0&&isFinite(M.getUTCFullYear())&&M.setUTCFullYear(A),M}function xA(A,M,t){var I=7+M-t,g=(7+uA(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=xA(A,I,g),N=1+7*(M-1)+T+E;return N<=0?(e=A-1,i=rA(e)+N):N>rA(A)?(e=A+1,i=N-rA(A)):(e=A,i=N),{year:e,dayOfYear:i}}function jA(A,M,t){var I,g,e=xA(A.year(),M,t),i=Math.floor((A.dayOfYear()-e-1)/7)+1;return i<1?(g=A.year()-1,I=i+lA(g,M,t)):i>lA(A.year(),M,t)?(I=i-lA(A.year(),M,t),g=A.year()+1):(g=A.year(),I=i),{week:I,year:g}}function lA(A,M,t){var I=xA(A,M,t),g=xA(A+1,M,t);return(rA(A)-I+g)/7}function wA(A){return jA(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=jA(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 fA(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=o([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=ng.call(this._weekdaysParse,i),g!==-1?g:null):"ddd"===M?(g=ng.call(this._shortWeekdaysParse,i),g!==-1?g:null):(g=ng.call(this._minWeekdaysParse,i),g!==-1?g:null):"dddd"===M?(g=ng.call(this._weekdaysParse,i),g!==-1?g:(g=ng.call(this._shortWeekdaysParse,i),g!==-1?g:(g=ng.call(this._minWeekdaysParse,i),g!==-1?g:null))):"ddd"===M?(g=ng.call(this._shortWeekdaysParse,i),g!==-1?g:(g=ng.call(this._weekdaysParse,i),g!==-1?g:(g=ng.call(this._minWeekdaysParse,i),g!==-1?g:null))):(g=ng.call(this._minWeekdaysParse,i),g!==-1?g:(g=ng.call(this._weekdaysParse,i),g!==-1?g:(g=ng.call(this._shortWeekdaysParse,i),g!==-1?g:null)))}function mA(A,M,t){var I,g,e;if(this._weekdaysParseExact)return fA.call(this,A,M,t);for(this._weekdaysParse||(this._weekdaysParse=[],this._minWeekdaysParse=[],this._shortWeekdaysParse=[],this._fullWeekdaysParse=[]),I=0;I<7;I++){if(g=o([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")||vA.call(this),A?this._weekdaysStrictRegex:this._weekdaysRegex):(N(this,"_weekdaysRegex")||(this._weekdaysRegex=xg),this._weekdaysStrictRegex&&A?this._weekdaysStrictRegex:this._weekdaysRegex)}function GA(A){return this._weekdaysParseExact?(N(this,"_weekdaysRegex")||vA.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")||vA.call(this),A?this._weekdaysMinStrictRegex:this._weekdaysMinRegex):(N(this,"_weekdaysMinRegex")||(this._weekdaysMinRegex=jg),this._weekdaysMinStrictRegex&&A?this._weekdaysMinStrictRegex:this._weekdaysMinRegex)}function vA(){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=o([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 bA(){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=lg._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=r(M)?tM(A):AM(A,M),t&&(lg=t)),lg._abbr}function AM(A,M){if(null!==M){var t=Yg;if(M.abbr=A,null!=dg[A])w("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 lg;if(!I(A)){if(M=_A(A))return M;A=[A]}return qA(A)}function IM(){return jI(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;MrA(g)&&(C(A)._overflowDayOfYear=!0),t=uA(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?uA:sA).apply(null,e),null!=A._tzm&&A._d.setUTCMinutes(A._d.getUTCMinutes()-A._tzm),A._nextDay&&(A._a[gg]=24)}}function nM(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],jA(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=jA(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>lA(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 oM(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),fI[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(!r(this._isDSTShifted))return this._isDSTShifted;var A={};if(B(A,this),A=rM(A),A._a){var M=A._isUTC?o(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 lM(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:x(T[Ig])*t,h:x(T[gg])*t,m:x(T[eg])*t,s:x(T[ig])*t,ms:x(wM(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=bM(sM(e.from),sM(e.to)),e={},e.ms=g.milliseconds,e.M=g.months),I=new jM(e),lM(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 vM(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 bM(A,M){var t;return A.isValid()&&M.isValid()?(M=dM(M,A),A.isBefore(M)?t=vM(A,M):(t=vM(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)||(w(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=wM(t._days),T=wM(t._months);A.isValid()&&(g=null==g||g,e&&A._d.setTime(A._d.valueOf()+e*I),i&&v(A,"Date",H(A,"Date")+i*I),T&&nA(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 Q(this)}function KM(A,M){var t=s(A)?A:sM(A);return!(!this.isValid()||!t.isValid())&&(M=F(r(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=uA(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 ft(A,M){M[Tg]=x(1e3*("0."+A))}function mt(){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=o().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 vt(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 bt(A,M){return Ht(A,M,"months")}function Xt(A,M){return Ht(A,M,"monthsShort")}function Wt(A,M,t){return vt(A,M,t,"weekdays")}function Vt(A,M,t){return vt(A,M,t,"weekdaysShort")}function Pt(A,M,t){return vt(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=u(e/1e3),E.seconds=A%60,M=u(A/60),E.minutes=M%60,t=u(M/60),E.hours=t%24,i+=u(t/24),g=u(MI(i)),T+=g,i-=$t(tI(g)),I=u(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*x(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 u(this.days()/7)}function NI(A,M,t,I,g){return g.relativeTime(M||1,!!t,A,I)}function nI(A,M,t){var I=GM(A).abs(),g=ae(I.as("s")),e=ae(I.as("m")),i=ae(I.as("h")),T=ae(I.as("d")),E=ae(I.as("M")),N=ae(I.as("y")),n=g0,n[4]=t,NI.apply(null,n)}function oI(A){return void 0===A?ae:"function"==typeof A&&(ae=A,!0)}function cI(A,M){return void 0!==De[A]&&(void 0===M?De[A]:(De[A]=M,!0))}function CI(A){var M=this.localeData(),t=nI(this,!A,M);return A&&(t=M.pastFuture(+this,t)),M.postformat(t)}function aI(){var A,M,t,I=re(this._milliseconds)/1e3,g=re(this._days),e=re(this._months);A=u(I/60),M=u(A/60),I%=60,A%=60,t=u(e/12),e%=12;var i=t,T=e,E=g,N=M,n=A,o=I,c=this.asSeconds();return c?(c<0?"-":"")+"P"+(i?i+"Y":"")+(T?T+"M":"")+(E?E+"D":"")+(N||n||o?"T":"")+(N?N+"H":"")+(n?n+"M":"")+(o?o+"S":""):"P0D"}var DI,rI;rI=Array.prototype.some?Array.prototype.some:function(A){for(var M=Object(this),t=M.length>>>0,I=0;I68?1900:2e3)};var rg=G("FullYear",!0);V("w",["ww",2],"wo","week"),V("W",["WW",2],"Wo","isoWeek"),m("week","w"),m("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)]=x(A)});var Bg={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"),m("day","d"),m("weekday","e"),m("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]=x(A)});var Qg="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),sg="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),ug="Su_Mo_Tu_We_Th_Fr_Sa".split("_"),xg=_I,yg=_I,jg=_I;V("H",["HH",2],0,"hour"),V("h",["hh",2],0,bA),V("k",["kk",2],0,XA),V("hmm",0,0,function(){return""+bA.apply(this)+W(this.minutes(),2)}),V("hmmss",0,0,function(){return""+bA.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),m("hour","h"),R("hour",13),_("a",VA),_("A",VA),_("H",GI),_("h",GI),_("HH",GI,FI),_("hh",GI,FI),_("hmm",HI),_("hmmss",vI),_("Hmm",HI),_("Hmmss",vI),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]=x(A),C(t).bigHour=!0}),tA("hmm",function(A,M,t){var I=A.length-2;M[gg]=x(A.substr(0,I)),M[eg]=x(A.substr(I)),C(t).bigHour=!0}),tA("hmmss",function(A,M,t){var I=A.length-4,g=A.length-2;M[gg]=x(A.substr(0,I)),M[eg]=x(A.substr(I,2)),M[ig]=x(A.substr(g)),C(t).bigHour=!0}),tA("Hmm",function(A,M,t){var I=A.length-2;M[gg]=x(A.substr(0,I)),M[eg]=x(A.substr(I))}),tA("Hmmss",function(A,M,t){var I=A.length-4,g=A.length-2;M[gg]=x(A.substr(0,I)),M[eg]=x(A.substr(I,2)),M[ig]=x(A.substr(g))});var lg,wg=/[ap]\.?m?\.?/i,Lg=G("Hours",!0),Yg={calendar:lI,longDateFormat:wI,invalidDate:LI,ordinal:YI,ordinalParse:dI,relativeTime:hI,months:cg,monthsShort:Cg,week:Bg,weekdays:Qg,weekdaysMin:ug,weekdaysShort:sg,meridiemParse:wg},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/]],fg=/^\/?Date\((\-?\d+)/i;M.createFromInputFallback=l("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 mg=l("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:D()}),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=jM.prototype;var Hg=XM(1,"add"),vg=XM(-1,"subtract");M.defaultFormat="YYYY-MM-DDTHH:mm:ssZ",M.defaultFormatUtc="YYYY-MM-DDTHH:mm:ss[Z]";var bg=l("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"),m("weekYear","gg"),m("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)]=x(A)}),IA(["gg","GG"],function(A,t,I,g){t[g]=M.parseTwoDigitYear(A)}),V("Q",0,"Qo","quarter"),m("quarter","Q"),R("quarter",7),_("Q",mI),tA("Q",function(A,M){M[tg]=3*(x(A)-1)}),V("D",["DD",2],"Do","date"),m("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]=x(A.match(GI)[0],10)});var Xg=G("Date",!0);V("DDD",["DDDD",3],"DDDo","dayOfYear"),m("dayOfYear","DDD"),R("dayOfYear",4),_("DDD",bI),_("DDDD",kI),tA(["DDD","DDDD"],function(A,M,t){t._dayOfYear=x(A)}),V("m",["mm",2],0,"minute"),m("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"),m("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()}),m("millisecond","ms"),R("millisecond",16),_("S",bI,mI),_("SS",bI,FI),_("SSS",bI,kI);var Pg;for(Pg="SSSS";Pg.length<=9;Pg+="S")_(Pg,VI);for(Pg="S";Pg.length<=9;Pg+="S")tA(Pg,ft);var Zg=G("Milliseconds",!1);V("z",0,0,"zoneAbbr"),V("zz",0,0,"zoneName");var Kg=Q.prototype;Kg.add=Hg,Kg.calendar=PM,Kg.clone=ZM,Kg.diff=tt,Kg.endOf=Dt,Kg.format=Tt,Kg.from=Et,Kg.fromNow=Nt,Kg.to=nt,Kg.toNow=ot,Kg.get=b,Kg.invalidAt=lt,Kg.isAfter=KM,Kg.isBefore=qM,Kg.isBetween=_M,Kg.isSame=$M,Kg.isSameOrAfter=At,Kg.isSameOrBefore=Mt,Kg.isValid=yt,Kg.lang=bg,Kg.locale=ct,Kg.localeData=Ct,Kg.max=Fg,Kg.min=mg,Kg.parsingFlags=jt,Kg.set=X,Kg.startOf=at,Kg.subtract=vg,Kg.toArray=st,Kg.toObject=ut,Kg.toDate=Qt,Kg.toISOString=et,Kg.inspect=it,Kg.toJSON=xt,Kg.toString=gt,Kg.unix=Bt,Kg.valueOf=rt,Kg.creationData=wt,Kg.year=rg,Kg.isLeapYear=QA,Kg.weekYear=Yt,Kg.isoWeekYear=dt,Kg.quarter=Kg.quarters=Ut,Kg.month=oA,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=fM,Kg.isDST=mM,Kg.isLocal=kM,Kg.isUtcOffset=RM,Kg.isUtc=JM,Kg.isUTC=JM,Kg.zoneAbbr=mt,Kg.zoneName=Ft,Kg.dates=l("dates accessor is deprecated. Use date instead.",Xg),Kg.months=l("months accessor is deprecated. Use month instead",oA),Kg.years=l("years accessor is deprecated. Use year instead",rg),Kg.zone=l("moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/",zM),Kg.isDSTShifted=l("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=f,qg.set=Y,qg.months=iA,qg.monthsShort=TA,qg.monthsParse=NA,qg.monthsRegex=aA,qg.monthsShortRegex=CA,qg.week=wA,qg.firstDayOfYear=YA,qg.firstDayOfWeek=LA,qg.weekdays=pA,qg.weekdaysMin=OA,qg.weekdaysShort=UA,qg.weekdaysParse=mA,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===x(A%100/10)?"th":1===M?"st":2===M?"nd":3===M?"rd":"th";return A+t}}),M.lang=l("moment.lang is deprecated. Use moment.locale instead.",$A),M.langData=l("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"),ne=TI("hours"),oe=TI("days"),ce=TI("months"),Ce=TI("years"),ae=Math.round,De={s:45,m:45,h:22,d:26,M:11},re=Math.abs,Be=jM.prototype;return Be.abs=Zt,Be.add=qt,Be.subtract=_t,Be.as=II,Be.asMilliseconds=$g,Be.asSeconds=Ae,Be.asMinutes=Me,Be.asHours=te,Be.asDays=Ie,Be.asWeeks=ge,Be.asMonths=ee,Be.asYears=ie,Be.valueOf=gI,Be._bubble=AI,Be.get=iI,Be.milliseconds=Te,Be.seconds=Ee,Be.minutes=Ne,Be.hours=ne,Be.days=oe,Be.weeks=EI,Be.months=ce,Be.years=Ce,Be.humanize=CI,Be.toISOString=aI,Be.toString=aI,Be.toJSON=aI,Be.locale=ct,Be.localeData=Ct,Be.toIsoString=l("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",aI),Be.lang=bg,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(x(A))}),M.version="2.17.1",t(sM),M.fn=Kg,M.min=xM,M.max=yM,M.now=kg,M.utc=o,M.unix=kt,M.months=bt,M.isDate=T,M.locale=$A,M.invalid=D,M.duration=GM,M.isMoment=s,M.weekdays=Wt,M.parseZone=Rt,M.localeData=tM,M.isDuration=lM,M.monthsShort=Xt,M.weekdaysMin=Pt,M.defineLocale=AM,M.updateLocale=MM,M.locales=IM,M.weekdaysShort=Vt,M.normalizeUnits=F,M.relativeTimeRounding=oI,M.relativeTimeThreshold=cI,M.calendarFormat=VM,M.prototype=Kg,M})}).call(M,t(215)(A))},function(A,M,t){"use strict";var I=t(284).default,g=t(285).default,e=t(180).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 n="default";M.DEFAULT=n;var o="primary";M.PRIMARY=o;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;t=0||Object.prototype.hasOwnProperty.call(A,I)&&(t[I]=A[I]);return t},M.__esModule=!0},function(A,M,t){"use strict";function I(A){return A&&A.__esModule?A:{default:A}}function g(A){return A.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function e(A){for(var M="",t=[],I=[],e=void 0,i=0,T=/:([a-zA-Z_$][a-zA-Z0-9_$]*)|\*\*|\*|\(|\)/g;e=T.exec(A);)e.index!==i&&(I.push(A.slice(i,e.index)),M+=g(A.slice(i,e.index))),e[1]?(M+="([^/]+)",t.push(e[1])):"**"===e[0]?(M+="(.*)",t.push("splat")):"*"===e[0]?(M+="(.*?)",t.push("splat")):"("===e[0]?M+="(?:":")"===e[0]&&(M+=")?"),I.push(e[0]),i=T.lastIndex;return i!==A.length&&(I.push(A.slice(i,A.length)),M+=g(A.slice(i,A.length))),{pattern:A,regexpSource:M,paramNames:t,tokens:I}}function i(A){return C[A]||(C[A]=e(A)),C[A]}function T(A,M){"/"!==A.charAt(0)&&(A="/"+A);var t=i(A),I=t.regexpSource,g=t.paramNames,e=t.tokens;"/"!==A.charAt(A.length-1)&&(I+="/?"),"*"===e[e.length-1]&&(I+="$");var T=M.match(new RegExp("^"+I,"i"));if(null==T)return null;var E=T[0],N=M.substr(E.length);if(N){if("/"!==E.charAt(E.length-1))return null;N="/"+N}return{remainingPathname:N,paramNames:g,paramValues:T.slice(1).map(function(A){return A&&decodeURIComponent(A)})}}function E(A){return i(A).paramNames}function N(A,M){var t=T(A,M);if(!t)return null;var I=t.paramNames,g=t.paramValues,e={};return I.forEach(function(A,M){e[A]=g[M]}),e}function n(A,M){M=M||{};for(var t=i(A),I=t.tokens,g=0,e="",T=0,E=void 0,N=void 0,n=void 0,o=0,C=I.length;o0?void 0:(0,c.default)(!1),null!=n&&(e+=encodeURI(n))):"("===E?g+=1:")"===E?g-=1:":"===E.charAt(0)?(N=E.substring(1),n=M[N],null!=n||g>0?void 0:(0,c.default)(!1),null!=n&&(e+=encodeURIComponent(n))):e+=E;return e.replace(/\/+/g,"/")}M.__esModule=!0,M.compilePattern=i,M.matchPattern=T,M.getParamNames=E,M.getParams=N,M.formatPattern=n;var o=t(16),c=I(o),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(3),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||{},n=A.DOMMutationMethods||{};A.isCustomAttribute&&T._isCustomAttributeFunctions.push(A.isCustomAttribute);for(var o in t){T.properties.hasOwnProperty(o)?g(!1):void 0;var c=o.toLowerCase(),C=t[o],a={attributeName:c,attributeNamespace:null,propertyName:o,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(a.mustUseAttribute&&a.mustUseProperty?g(!1):void 0,!a.mustUseProperty&&a.hasSideEffects?g(!1):void 0,a.hasBooleanValue+a.hasNumericValue+a.hasOverloadedBooleanValue<=1?void 0:g(!1),E.hasOwnProperty(o)){var D=E[o];a.attributeName=D}i.hasOwnProperty(o)&&(a.attributeNamespace=i[o]),N.hasOwnProperty(o)&&(a.propertyName=N[o]),n.hasOwnProperty(o)&&(a.mutationMethod=n[o]),T.properties[o]=a}}},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&&n(A,e,t,I,!1,!0),e!==M&&n(e,M,t,g,!0,!1)},traverseTwoPhase:function(A,M,t){A&&(n("",A,M,t,!0,!1),n(A,"",M,t,!1,!0))},traverseTwoPhaseSkipTarget:function(A,M,t){A&&(n("",A,M,t,!0,!0),n(A,"",M,t,!0,!0))},traverseAncestors:function(A,M,t){n("",A,M,t,!0,!1)},getFirstCommonAncestorID:N,_getNextDescendantID:E,isAncestorIDOf:i,SEPARATOR:C};A.exports=r},function(A,M,t){var I=t(35),g=t(11)("toStringTag"),e="Arguments"==I(function(){return arguments}()),i=function(A,M){try{return A[M]}catch(A){}};A.exports=function(A){var M,t,T;return void 0===A?"Undefined":null===A?"Null":"string"==typeof(t=i(M=Object(A),g))?t:e?I(M):"Object"==(T=I(M))&&"function"==typeof M.callee?"Arguments":T}},function(A,M,t){var I=t(35);A.exports=Object("z").propertyIsEnumerable(0)?Object:function(A){return"String"==I(A)?A.split(""):Object(A)}},function(A,M){M.f={}.propertyIsEnumerable},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 o.default.Children.map(A,function(A){if(o.default.isValidElement(A)){var g=I;return I++,M.call(t,A,g)}return A})}function g(A,M,t){var I=0;return o.default.Children.forEach(A,function(A){o.default.isValidElement(A)&&(M.call(t,A,I),I++)})}function e(A){var M=0;return o.default.Children.forEach(A,function(A){o.default.isValidElement(A)&&M++}),M}function i(A){var M=!1;return o.default.Children.forEach(A,function(A){!M&&o.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 o.default.Children.forEach(A,function(A){o.default.isValidElement(A)&&(M.call(t,A,I)&&g.push(A),I++)}),g}var N=t(12).default;M.__esModule=!0;var n=t(2),o=N(n);M.default={map:I,forEach:g,numberOf:e,find:T,findValidComponents:E,hasValidComponent:i},A.exports=M.default},function(A,M){var t=A.exports={version:"1.2.6"};"number"==typeof __e&&(__e=t)},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(34),e=I(g),i=t(83),T=I(i);A.exports=M.default},function(A,M,t){"use strict";var I=t(316),g=t(729),e=t(329),i=t(338),T=t(339),E=t(3),N=(t(7),{}),n=null,o=function(A,M){A&&(g.executeDispatchesInOrder(A,M),A.isPersistent()||A.constructor.release(A))},c=function(A){return o(A,!0)},C=function(A){return o(A,!1)},a=null,D={injection:{injectMount:g.injection.injectMount,injectInstanceHandle:function(A){a=A},getInstanceHandle:function(){return a},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;Nn;)if(T=E[n++],T!=T)return!0}else for(;N>n;n++)if((A||n in E)&&E[n]===t)return A||n||0;return!A&&-1}}},function(A,M,t){"use strict";var I=t(5),g=t(1),e=t(26),i=t(68),T=t(55),E=t(79),N=t(63),n=t(9),o=t(6),c=t(111),C=t(81),a=t(143);A.exports=function(A,M,t,D,r,B){var Q=I[A],s=Q,u=r?"set":"add",x=s&&s.prototype,y={},j=function(A){var M=x[A];e(x,A,"delete"==A?function(A){return!(B&&!n(A))&&M.call(this,0===A?0:A)}:"has"==A?function(A){return!(B&&!n(A))&&M.call(this,0===A?0:A)}:"get"==A?function(A){return B&&!n(A)?void 0:M.call(this,0===A?0:A)}:"add"==A?function(A){return M.call(this,0===A?0:A),this}:function(A,t){return M.call(this,0===A?0:A,t),this})};if("function"==typeof s&&(B||x.forEach&&!o(function(){(new s).entries().next()}))){var l=new s,w=l[u](B?{}:-0,1)!=l,L=o(function(){l.has(1)}),Y=c(function(A){new s(A)}),d=!B&&o(function(){for(var A=new s,M=5;M--;)A[u](M,M);return!A.has(-0)});Y||(s=M(function(M,t){N(M,s,A);var I=a(new Q,M,s);return void 0!=t&&E(t,r,I[u],I),I}),s.prototype=x,x.constructor=s),(L||d)&&(j("delete"),j("has"),r&&j("get")),(d||w)&&j(u),B&&x.clear&&delete x.clear}else s=D.getConstructor(M,A,r,u),i(s.prototype,t),T.NEED=!0;return C(s,A),y[A]=s,g(g.G+g.W+g.F*(s!=Q),y),B||D.setStrong(s,A,r),s}},function(A,M,t){"use strict";var I=t(25),g=t(26),e=t(6),i=t(36),T=t(11);A.exports=function(A,M,t){var E=T(A),N=t(i,E,""[A]),n=N[0],o=N[1];e(function(){var M={};return M[E]=function(){return 7},7!=""[A](M)})&&(g(String.prototype,A,n),I(RegExp.prototype,E,2==M?function(A,M){return o.call(A,this,M)}:function(A){return o.call(A,this)}))}},function(A,M,t){"use strict";var I=t(4);A.exports=function(){var A=I(this),M="";return A.global&&(M+="g"),A.ignoreCase&&(M+="i"),A.multiline&&(M+="m"),A.unicode&&(M+="u"),A.sticky&&(M+="y"),M}},function(A,M){A.exports=function(A,M,t){var I=void 0===t;switch(M.length){case 0:return I?A():A.call(t);case 1:return I?A(M[0]):A.call(t,M[0]);case 2:return I?A(M[0],M[1]):A.call(t,M[0],M[1]);case 3:return I?A(M[0],M[1],M[2]):A.call(t,M[0],M[1],M[2]);case 4:return I?A(M[0],M[1],M[2],M[3]):A.call(t,M[0],M[1],M[2],M[3])}return A.apply(t,M)}},function(A,M,t){var I=t(9),g=t(35),e=t(11)("match");A.exports=function(A){var M;return I(A)&&(void 0!==(M=A[e])?!!M:"RegExp"==g(A))}},function(A,M,t){var I=t(11)("iterator"),g=!1;try{var e=[7][I]();e.return=function(){g=!0},Array.from(e,function(){throw 2})}catch(A){}A.exports=function(A,M){if(!M&&!g)return!1;var t=!1;try{var e=[7],i=e[I]();i.next=function(){return{done:t=!0}},e[I]=function(){return i},A(e)}catch(A){}return t}},function(A,M,t){A.exports=t(64)||!t(6)(function(){var A=Math.random();__defineSetter__.call(null,A,function(){}),delete t(5)[A]})},function(A,M){M.f=Object.getOwnPropertySymbols},function(A,M,t){var I=t(5),g="__core-js_shared__",e=I[g]||(I[g]={});A.exports=function(A){return e[A]||(e[A]={})}},function(A,M,t){for(var I,g=t(5),e=t(25),i=t(71),T=i("typed_array"),E=i("view"),N=!(!g.ArrayBuffer||!g.DataView),n=N,o=0,c=9,C="Int8Array,Uint8Array,Uint8ClampedArray,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array".split(",");o1?I-1:0),e=1;e":">","<":"<",'"':""","'":"'"},e=/[&><"']/g;A.exports=I},function(A,M,t){"use strict";var I=t(20),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(2),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,n=A.readonly,o=A.autoComplete,c=A.align,C=A.className,a=e.default.createElement("input",{id:t,name:I,value:g,onChange:i,className:"ig-text",type:T,spellCheck:E,required:N,autoComplete:o});return n&&(a=e.default.createElement("input",{id:t,name:I,value:g,onChange:i,className:"ig-text",type:T,spellCheck:E,required:N,autoComplete:o,disabled:!0})),e.default.createElement("div",{className:"input-group "+c+" "+C},a,e.default.createElement("i",{className:"ig-helpers"}),e.default.createElement("label",{className:"ig-label"},M))};M.default=i},function(A,M,t){"use strict";var I=t(19),g=t(70),e=t(17);A.exports=function(A){for(var M=I(this),t=e(M.length),i=arguments.length,T=g(i>1?arguments[1]:void 0,t),E=i>2?arguments[2]:void 0,N=void 0===E?t:g(E,t);N>T;)M[T++]=A;return M}},function(A,M,t){"use strict";var I=t(14),g=t(56);A.exports=function(A,M,t){M in A?I.f(A,M,g(0,t)):A[M]=t}},function(A,M,t){var I=t(9),g=t(5).document,e=I(g)&&I(g.createElement);A.exports=function(A){return e?g.createElement(A):{}}},function(A,M){A.exports="constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split(",")},function(A,M,t){var I=t(11)("match");A.exports=function(A){var M=/./;try{"/./"[A](M)}catch(t){try{return M[I]=!1,!"/./"[A](M)}catch(A){}}return!0}},function(A,M,t){A.exports=t(5).document&&document.documentElement},function(A,M,t){var I=t(9),g=t(151).set;A.exports=function(A,M,t){var e,i=M.constructor;return i!==t&&"function"==typeof i&&(e=i.prototype)!==t.prototype&&I(e)&&g&&g(A,e),A}},function(A,M,t){var I=t(80),g=t(11)("iterator"),e=Array.prototype;A.exports=function(A){return void 0!==A&&(I.Array===A||e[g]===A)}},function(A,M,t){var I=t(35);A.exports=Array.isArray||function(A){return"Array"==I(A)}},function(A,M,t){"use strict";var I=t(65),g=t(56),e=t(81),i={};t(25)(i,t(11)("iterator"),function(){return this}),A.exports=function(A,M,t){A.prototype=I(i,{next:g(1,t)}),e(A,M+" Iterator")}},function(A,M,t){"use strict";var I=t(64),g=t(1),e=t(26),i=t(25),T=t(21),E=t(80),N=t(146),n=t(81),o=t(31),c=t(11)("iterator"),C=!([].keys&&"next"in[].keys()),a="@@iterator",D="keys",r="values",B=function(){return this};A.exports=function(A,M,t,Q,s,u,x){N(t,M,Q);var y,j,l,w=function(A){if(!C&&A in h)return h[A];switch(A){case D:return function(){return new t(this,A)};case r:return function(){return new t(this,A)}}return function(){return new t(this,A)}},L=M+" Iterator",Y=s==r,d=!1,h=A.prototype,S=h[c]||h[a]||s&&h[s],z=S||w(s),p=s?Y?w("entries"):z:void 0,U="Array"==M?h.entries||S:S;if(U&&(l=o(U.call(new A)),l!==Object.prototype&&(n(l,L,!0),I||T(l,c)||i(l,c,B))),Y&&S&&S.name!==r&&(d=!0,z=function(){return S.call(this)}),I&&!x||!C&&!d&&h[c]||i(h,c,z),E[M]=z,E[L]=B,s)if(y={values:Y?z:w(r),keys:u?z:w(D),entries:p},x)for(j in y)j in h||e(h,j,y[j]);else g(g.P+g.F*(C||d),M,y);return y}},function(A,M){var t=Math.expm1;A.exports=!t||t(10)>22025.465794806718||t(10)<22025.465794806718||t(-2e-17)!=-2e-17?function(A){return 0==(A=+A)?A:A>-1e-6&&A<1e-6?A+A*A/2:Math.exp(A)-1}:t},function(A,M){A.exports=Math.sign||function(A){return 0==(A=+A)||A!=A?A:A<0?-1:1}},function(A,M,t){var I=t(5),g=t(158).set,e=I.MutationObserver||I.WebKitMutationObserver,i=I.process,T=I.Promise,E="process"==t(35)(i);A.exports=function(){var A,M,t,N=function(){var I,g;for(E&&(I=i.domain)&&I.exit();A;){g=A.fn,A=A.next;try{g()}catch(I){throw A?t():M=void 0,I}}M=void 0,I&&I.enter()};if(E)t=function(){i.nextTick(N)};else if(e){var n=!0,o=document.createTextNode("");new e(N).observe(o,{characterData:!0}),t=function(){o.data=n=!n}}else if(T&&T.resolve){var c=T.resolve();t=function(){c.then(N)}}else t=function(){g.call(I,N)};return function(I){var g={fn:I,next:void 0};M&&(M.next=g),A||(A=g,t()),M=g}}},function(A,M,t){var I=t(9),g=t(4),e=function(A,M){if(g(A),!I(M)&&null!==M)throw TypeError(M+": can't set as prototype!")};A.exports={set:Object.setPrototypeOf||("__proto__"in{}?function(A,M,I){try{I=t(49)(Function.call,t(30).f(Object.prototype,"__proto__").set,2),I(A,[]),M=!(A instanceof Array)}catch(A){M=!0}return function(A,t){return e(A,t),M?A.__proto__=t:I(A,t),A}}({},!1):void 0),check:e}},function(A,M,t){var I=t(114)("keys"),g=t(71);A.exports=function(A){return I[A]||(I[A]=g(A))}},function(A,M,t){var I=t(4),g=t(24),e=t(11)("species");A.exports=function(A,M){var t,i=I(A).constructor;return void 0===i||void 0==(t=I(i)[e])?M:g(t)}},function(A,M,t){var I=t(57),g=t(36);A.exports=function(A){return function(M,t){var e,i,T=String(g(M)),E=I(t),N=T.length;return E<0||E>=N?A?"":void 0:(e=T.charCodeAt(E),e<55296||e>56319||E+1===N||(i=T.charCodeAt(E+1))<56320||i>57343?A?T.charAt(E):e:A?T.slice(E,E+2):(e-55296<<10)+(i-56320)+65536)}}},function(A,M,t){var I=t(110),g=t(36);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){"use strict";var I=t(57),g=t(36);A.exports=function(A){var M=String(g(this)),t="",e=I(A);if(e<0||e==1/0)throw RangeError("Count can't be negative");for(;e>0;(e>>>=1)&&(M+=M))1&e&&(t+=M);return t}},function(A,M){A.exports="\t\n\v\f\r   ᠎              \u2028\u2029\ufeff"},function(A,M,t){var I,g,e,i=t(49),T=t(109),E=t(142),N=t(139),n=t(5),o=n.process,c=n.setImmediate,C=n.clearImmediate,a=n.MessageChannel,D=0,r={},B="onreadystatechange",Q=function(){var A=+this;if(r.hasOwnProperty(A)){var M=r[A];delete r[A],M()}},s=function(A){Q.call(A.data)};c&&C||(c=function(A){for(var M=[],t=1;arguments.length>t;)M.push(arguments[t++]);return r[++D]=function(){T("function"==typeof A?A:Function(A),M)},I(D),D},C=function(A){delete r[A]},"process"==t(35)(o)?I=function(A){o.nextTick(i(Q,A,1))}:a?(g=new a,e=g.port2,g.port1.onmessage=s,I=i(e.postMessage,e,1)):n.addEventListener&&"function"==typeof postMessage&&!n.importScripts?(I=function(A){n.postMessage(A+"","*")},n.addEventListener("message",s,!1)):I=B in N("script")?function(A){E.appendChild(N("script"))[B]=function(){E.removeChild(this),Q.call(A)}}:function(A){setTimeout(i(Q,A,1),0)}),A.exports={set:c,clear:C}},function(A,M,t){"use strict";var I=t(5),g=t(13),e=t(64),i=t(115),T=t(25),E=t(68),N=t(6),n=t(63),o=t(57),c=t(17),C=t(66).f,a=t(14).f,D=t(137),r=t(81),B="ArrayBuffer",Q="DataView",s="prototype",u="Wrong length!",x="Wrong index!",y=I[B],j=I[Q],l=I.Math,w=I.RangeError,L=I.Infinity,Y=y,d=l.abs,h=l.pow,S=l.floor,z=l.log,p=l.LN2,U="buffer",O="byteLength",f="byteOffset",m=g?"_b":U,F=g?"_l":O,k=g?"_o":f,R=function(A,M,t){var I,g,e,i=Array(t),T=8*t-M-1,E=(1<>1,n=23===M?h(2,-24)-h(2,-77):0,o=0,c=A<0||0===A&&1/A<0?1:0;for(A=d(A),A!=A||A===L?(g=A!=A?1:0,I=E):(I=S(z(A)/p),A*(e=h(2,-I))<1&&(I--,e*=2),A+=I+N>=1?n/e:n*h(2,1-N),A*e>=2&&(I++,e/=2),I+N>=E?(g=0,I=E):I+N>=1?(g=(A*e-1)*h(2,M),I+=N):(g=A*h(2,N-1)*h(2,M),I=0));M>=8;i[o++]=255&g,g/=256,M-=8);for(I=I<0;i[o++]=255&I,I/=256,T-=8);return i[--o]|=128*c,i},J=function(A,M,t){var I,g=8*t-M-1,e=(1<>1,T=g-7,E=t-1,N=A[E--],n=127&N;for(N>>=7;T>0;n=256*n+A[E],E--,T-=8);for(I=n&(1<<-T)-1,n>>=-T,T+=M;T>0;I=256*I+A[E],E--,T-=8);if(0===n)n=1-i;else{if(n===e)return I?NaN:N?-L:L;I+=h(2,M),n-=i}return(N?-1:1)*I*h(2,n-M)},G=function(A){return A[3]<<24|A[2]<<16|A[1]<<8|A[0]},H=function(A){return[255&A]},v=function(A){return[255&A,A>>8&255]},b=function(A){return[255&A,A>>8&255,A>>16&255,A>>24&255]},X=function(A){return R(A,52,8)},W=function(A){return R(A,23,4)},V=function(A,M,t){a(A[s],M,{get:function(){return this[t]}})},P=function(A,M,t,I){var g=+t,e=o(g);if(g!=e||e<0||e+M>A[F])throw w(x);var i=A[m]._b,T=e+A[k],E=i.slice(T,T+M);return I?E:E.reverse()},Z=function(A,M,t,I,g,e){var i=+t,T=o(i);if(i!=T||T<0||T+M>A[F])throw w(x);for(var E=A[m]._b,N=T+A[k],n=I(+g),c=0;cAA;)(q=$[AA++])in y||T(y,q,Y[q]);e||(_.constructor=y)}var MA=new j(new y(2)),tA=j[s].setInt8;MA.setInt8(0,2147483648),MA.setInt8(1,2147483649),!MA.getInt8(0)&&MA.getInt8(1)||E(j[s],{setInt8:function(A,M){tA.call(this,A,M<<24>>24)},setUint8:function(A,M){tA.call(this,A,M<<24>>24)}},!0)}else y=function(A){var M=K(this,A);this._b=D.call(Array(M),0),this[F]=M},j=function(A,M,t){n(this,j,Q),n(A,y,Q);var I=A[F],g=o(M);if(g<0||g>I)throw w("Wrong offset!");if(t=void 0===t?I-g:c(t),g+t>I)throw w(u);this[m]=A,this[k]=g,this[F]=t},g&&(V(y,O,"_l"),V(j,U,"_b"),V(j,O,"_l"),V(j,f,"_o")),E(j[s],{getInt8:function(A){return P(this,1,A)[0]<<24>>24},getUint8:function(A){return P(this,1,A)[0]},getInt16:function(A){var M=P(this,2,A,arguments[1]);return(M[1]<<8|M[0])<<16>>16},getUint16:function(A){var M=P(this,2,A,arguments[1]);return M[1]<<8|M[0]},getInt32:function(A){return G(P(this,4,A,arguments[1]))},getUint32:function(A){return G(P(this,4,A,arguments[1]))>>>0},getFloat32:function(A){return J(P(this,4,A,arguments[1]),23,4)},getFloat64:function(A){return J(P(this,8,A,arguments[1]),52,8)},setInt8:function(A,M){Z(this,1,A,H,M)},setUint8:function(A,M){Z(this,1,A,H,M)},setInt16:function(A,M){Z(this,2,A,v,M,arguments[2])},setUint16:function(A,M){Z(this,2,A,v,M,arguments[2])},setInt32:function(A,M){Z(this,4,A,b,M,arguments[2])},setUint32:function(A,M){Z(this,4,A,b,M,arguments[2])},setFloat32:function(A,M){Z(this,4,A,W,M,arguments[2])},setFloat64:function(A,M){Z(this,8,A,X,M,arguments[2])}});r(y,B),r(j,Q),T(j[s],i.VIEW,!0),M[B]=y,M[Q]=j},function(A,M,t){var I=t(5),g=t(48),e=t(64),i=t(240),T=t(14).f;A.exports=function(A){var M=g.Symbol||(g.Symbol=e?{}:I.Symbol||{});"_"==A.charAt(0)||A in M||T(M,A,{value:i.f(A)})}},function(A,M,t){var I=t(94),g=t(11)("iterator"),e=t(80);A.exports=t(48).getIteratorMethod=function(A){if(void 0!=A)return A[g]||A["@@iterator"]||e[I(A)]}},function(A,M,t){"use strict";var I=t(78),g=t(228),e=t(80),i=t(28);A.exports=t(147)(Array,"Array",function(A,M){this._t=i(A),this._i=0,this._k=M},function(){var A=this._t,M=this._k,t=this._i++;return!A||t>=A.length?(this._t=void 0,g(1)):"keys"==M?g(0,t):"values"==M?g(0,A[t]):g(0,[t,A[t]])},"values"),e.Arguments=e.Array,I("keys"),I("values"),I("entries")},function(A,M,t){"use strict";var I=t(72),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(252),g=t(571),e=t(566),i=t(567),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 n in N)T.call(N,n)&&(N[n]||0===N[n]?E+=g(n)+":"+N[n]+";":i(A,g(n)));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(n.L()?i[n.n()]:e[n.n()])+n.j()-1},W:function(){var A=n.z()-n.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(n.n(),2,"0")},M:function(){return n.F().slice(0,3)},n:function(){return t.getMonth()+1},t:function(){return new Date(n.Y(),n.n(),0).getDate()},L:function(){return 1===new Date(n.Y(),1,29).getMonth()?1:0},o:function(){var A=n.n(),M=n.W();return n.Y()+(12===A&&M<9?-1:1===A&&M>9)},Y:function(){return t.getFullYear()},y:function(){return String(n.Y()).slice(-2)},a:function(){return t.getHours()>11?"pm":"am"},A:function(){return n.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 n.G()%12||12},G:function(){return t.getHours()},h:function(){return g.pad(n.g(),2,"0")},H:function(){return g.pad(n.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=n.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),n=12*E+parseInt(g.date("n",A),10),o=N-n;if(o<12&&o>-12)return o>=0?o+" months ago":"in "+-o+" 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,B=c.enumErrorProps&&(A===j||A instanceof Error),Q=c.enumPrototypes&&T(A);++I1)for(var t=1;tM.documentElement.clientHeight;return{modalStyles:{paddingRight:I&&!g?B.default():void 0,paddingLeft:!I&&g?B.default():void 0}}}});b.Body=z.default,b.Header=U.default,b.Title=f.default,b.Footer=F.default,b.Dialog=h.default,b.TRANSITION_DURATION=300,b.BACKDROP_TRANSITION_DURATION=150,M.default=C.bsSizes([D.Sizes.LARGE,D.Sizes.SMALL],C.bsClass("modal",b)),A.exports=M.default},function(A,M,t){"use strict";var I=t(33).default,g=t(32).default,e=t(89).default,i=t(15).default,T=t(12).default;M.__esModule=!0;var E=t(2),N=T(E),n=t(10),o=T(n),c=t(22),C=T(c),a=t(88),D=T(a),r=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=D.default(this.context.$bs_onModalHide,this.props.onHide);return N.default.createElement("div",i({},t,{className:o.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);r.propTypes={"aria-label":N.default.PropTypes.string,bsClass:N.default.PropTypes.string,closeButton:N.default.PropTypes.bool,onHide:N.default.PropTypes.func},r.contextTypes={$bs_onModalHide:N.default.PropTypes.func},r.defaultProps={"aria-label":"Close",closeButton:!1},M.default=c.bsClass("modal",r),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(15).default,e=t(180).default,i=t(12).default;M.__esModule=!0;var T=t(84),E=i(T),N=t(277),n=i(N),o=t(2),c=i(o),C=t(34),a=i(C),D=t(214),r=(i(D),t(654)),B=i(r),Q=t(88),s=i(Q),u=c.default.createClass({displayName:"OverlayTrigger",propTypes:g({},B.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(){a.default.unstable_renderSubtreeIntoContainer(this,this._overlay,this._mountNode)},componentWillUnmount:function(){a.default.unmountComponentAtNode(this._mountNode),this._mountNode=null,clearTimeout(this._hoverShowDelay),clearTimeout(this._hoverHideDelay)},componentDidUpdate:function(){this._mountNode&&this.renderOverlay(); -},getOverlayTarget:function(){return a.default.findDOMNode(this)},getOverlay:function(){var A=g({},n.default(this.props,e(B.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=o.cloneElement(this.props.overlay,{placement:A.placement,container:A.container});return c.default.createElement(B.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)),o.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=u,A.exports=M.default},function(A,M,t){"use strict";var I=t(15).default,g=t(12).default;M.__esModule=!0;var e=t(2),i=g(e),T=t(10),E=g(T),N=t(22),n=g(N),o=t(296),c=g(o),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[n.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:n.default.prefix(this.props,"arrow"),style:g}),i.default.createElement("div",{className:n.default.prefix(this.props,"inner")},this.props.children))}});M.default=C,A.exports=M.default},function(A,M,t){A.exports={default:t(661),__esModule:!0}},function(A,M,t){var I=t(667),g=t(99),e=t(286),i="prototype",T=function(A,M,t){var E,N,n,o=A&T.F,c=A&T.G,C=A&T.S,a=A&T.P,D=A&T.B,r=A&T.W,B=c?g:g[M]||(g[M]={}),Q=c?I:C?I[M]:(I[M]||{})[i];c&&(t=M);for(E in t)N=!o&&Q&&E in Q,N&&E in B||(n=N?Q[E]:t[E],B[E]=c&&"function"!=typeof Q[E]?t[E]:D&&N?e(n,I):r&&Q[E]==n?function(A){var M=function(M){return this instanceof A?new A(M):A(M)};return M[i]=A[i],M}(n):a&&"function"==typeof n?e(Function.call,n):n,a&&((B[i]||(B[i]={}))[E]=n))};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";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(34),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(2),T=I(i),E=t(295),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(2),i=t(125),T=(g(i),t(74)),E=I(T),N=t(18),n=(g(N),e.PropTypes.func),o=e.PropTypes.object,c=e.PropTypes.shape,C=e.PropTypes.string,a=M.routerShape=c({push:n.isRequired,replace:n.isRequired,go:n.isRequired,goBack:n.isRequired,goForward:n.isRequired,setRouteLeaveHook:n.isRequired,isActive:n.isRequired}),D=M.locationShape=c({pathname:C.isRequired,search:C.isRequired,state:o,action:C.isRequired,key:C}),r=M.falsy=E.falsy,B=M.history=E.history,Q=M.location=D,s=M.component=E.component,u=M.components=E.components,x=M.route=E.route,y=(M.routes=E.routes,M.router=a),j={falsy:r,history:B,location:Q,component:s,components:u,route:x,router:y};M.default=j},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){u&&u.location===A?e(u,t):(0,r.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,a.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,n.runLeaveHooks)(e,s),e.filter(function(A){return E.indexOf(A)===-1}).forEach(D),(0,n.runChangeHooks)(T,s,A,function(M,g){return M||g?I(M,g):void(0,n.runEnterHooks)(E,A,t)})}function T(A){var M=arguments.length<=1||void 0===arguments[1]||arguments[1];return A.__id__||M&&(A.__id__=x++)}function E(A){return A.reduce(function(A,M){return A.push.apply(A,y[T(M)]),A},[])}function o(A,t){(0,r.default)(M,A,function(M,I){if(null==I)return void t();u=i({},I,{location:A});for(var g=E((0,N.default)(s,u).leaveRoutes),e=void 0,T=0,n=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 n}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(735),i=t(327),T=t(333),E=t(8),N=t(3),n=(t(7),function(){});E(n.prototype,e.Mixin,{_instantiateReactComponent:g}),A.exports=g},function(A,M,t){"use strict";/** +!function(){"use strict";function t(){for(var A=[],M=0;M0?g(I(A),9007199254740991):0}},function(A,M,t){"use strict";function I(A){return A&&A.__esModule?A:{default:A}}function g(A,M){if(M.indexOf("deprecated")!==-1){if(E[M])return;E[M]=!0}M="[react-router] "+M;for(var t=arguments.length,I=Array(t>2?t-2:0),g=2;g"+g+""};A.exports=function(A,M){var t={};t[A]=M(T),I(I.P+I.F*g(function(){var M=""[A]('"');return M!==M.toLowerCase()||M.split('"').length>3}),"String",t)}},function(A,M,t){var I=t(95),g=t(36);A.exports=function(A){return I(g(A))}},function(A,M,t){"use strict";var I=t(53),g=t(8),e=(t(133),"function"==typeof Symbol&&Symbol.for&&Symbol.for("react.element")||60103),i={key:!0,ref:!0,__self:!0,__source:!0},T=function(A,M,t,I,g,i,T){var E={$$typeof:e,type:A,key:M,ref:t,props:T,_owner:i};return E};T.createElement=function(A,M,t){var g,e={},E=null,N=null,n=null,o=null;if(null!=M){N=void 0===M.ref?null:M.ref,E=void 0===M.key?null:""+M.key,n=void 0===M.__self?null:M.__self,o=void 0===M.__source?null:M.__source;for(g in M)M.hasOwnProperty(g)&&!i.hasOwnProperty(g)&&(e[g]=M[g])}var c=arguments.length-2;if(1===c)e.children=t;else if(c>1){for(var C=Array(c),a=0;a1){for(var D=Array(a),r=0;r1?"-":"";g.href=e,g.download=M.bucketName+i+M.prefix.slice(0,-1)+".zip",g.click(),window.URL.revokeObjectURL(e),g.remove()}},t.send(JSON.stringify(M))}},M.uploadFile=function(A,M){return function(t,I){var g=I(),i=g.currentBucket,T=g.currentPath,E=""+T+A.name,n=window.location.origin+"/minio/upload/"+i+"/"+E,o=i+"-"+T+"-"+A.name;M.open("PUT",n,!0),M.withCredentials=!1;var c=N.default.getItem("token");c&&M.setRequestHeader("Authorization","Bearer "+N.default.getItem("token")),M.setRequestHeader("x-amz-date",(0,e.default)().utc().format("YYYYMMDDTHHmmss")+"Z"),t(eA({slug:o,xhr:M,size:A.size,name:A.name})),M.onload=function(I){401!=M.status&&403!=M.status&&500!=M.status||(EA(!1),t(iA({slug:o})),t(_({type:"danger",message:"Unauthorized request."}))),200==M.status&&(EA(!1),t(iA({slug:o})),t(_({type:"success",message:"File '"+A.name+"' uploaded successfully."})),t(gA(T)))},M.upload.addEventListener("error",function(M){t(_({type:"danger",message:"Error occurred uploading '"+A.name+"'."})),t(iA({slug:o}))}),M.upload.addEventListener("progress",function(A){if(A.lengthComputable){var M=A.loaded;A.total;t(TA({slug:o,loaded:M}))}}),M.send(A)}},M.showAbout=function(){return{type:h,showAbout:!0}},M.hideAbout=function(){return{type:h,showAbout:!1}},M.setSortNameOrder=function(A){return{type:S,sortNameOrder:A}}),nA=(M.setSortSizeOrder=function(A){return{type:z,sortSizeOrder:A}},M.setSortDateOrder=function(A){return{type:p,sortDateOrder:A}},M.setLatestUIVersion=function(A){return{type:U,latestUiVersion:A}},M.showSettings=function(){return{type:k,showSettings:!0}},M.hideSettings=function(){return{type:k,showSettings:!1}},M.setSettings=function(A){return{type:R,settings:A}},M.showBucketPolicy=function(){return{type:J,showBucketPolicy:!0}},M.hideBucketPolicy=function(){return{type:J,showBucketPolicy:!1}},M.setPolicies=function(A){return{type:G,policies:A}},M.checkedObjectsAdd=function(A){return{type:W,objectName:A}},M.checkedObjectsRemove=function(A){return{type:V,objectName:A}},M.checkedObjectsReset=function(A){return{type:P,objectName:A}})},function(A,M,t){var I=t(49),g=t(95),e=t(19),i=t(17),T=t(373);A.exports=function(A,M){var t=1==A,E=2==A,N=3==A,n=4==A,o=6==A,c=5==A||o,C=M||T;return function(M,T,a){for(var D,r,B=e(M),Q=g(B),s=I(T,a,3),u=i(Q.length),x=0,y=t?C(M,u):E?C(M,0):void 0;u>x;x++)if((c||x in Q)&&(D=Q[x],r=s(D,x,B),A))if(t)y[x]=r;else if(r)switch(A){case 3:return!0;case 5:return D;case 6:return x;case 2:y.push(D)}else if(n)return!1;return o?-1:N||n?n:y}}},function(A,M,t){var I=t(1),g=t(48),e=t(6);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(9);A.exports=function(A,M){if(!I(A))return A;var t,g;if(M&&"function"==typeof(t=A.toString)&&!I(g=t.call(A)))return g;if("function"==typeof(t=A.valueOf)&&!I(g=t.call(A)))return g;if(!M&&"function"==typeof(t=A.toString)&&!I(g=t.call(A)))return g;throw TypeError("Can't convert object to primitive value")}},function(A,M){"use strict";function t(A){return function(){return A}}function I(){}I.thatReturns=t,I.thatReturnsFalse=t(!1),I.thatReturnsTrue=t(!0),I.thatReturnsNull=t(null),I.thatReturnsThis=function(){return this},I.thatReturnsArgument=function(A){return A},A.exports=I},function(A,M,t){function I(A){if(i.unindexedChars&&e(A)){for(var M=-1,t=A.length,I=Object(A);++M3&&void 0!==arguments[3]?arguments[3]:{},N=Boolean(A),c=A||l,a=void 0;a="function"==typeof M?M:M?(0,B.default)(M):w;var r=t||L,Q=I.pure,s=void 0===Q||Q,u=I.withRef,y=void 0!==u&&u,h=s&&r!==L,S=d++;return function(A){function M(A,M,t){var I=r(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,j.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=a(A.dispatch,M),I="function"==typeof t;return this.finalMapDispatchToProps=I?t:a,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,D.default)(A,this.stateProps))&&(this.stateProps=A,!0)},T.prototype.updateDispatchPropsIfNeeded=function(){var A=this.computeDispatchProps(this.store,this.props);return(!this.dispatchProps||!(0,D.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,D.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,D.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,j.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,o.createElement)(A,n({},this.mergedProps,{ref:"wrappedInstance"})):this.renderedElement=(0,o.createElement)(A,this.mergedProps),this.renderedElement)},T}(o.Component); +return I.displayName=t,I.WrappedComponent=A,I.contextTypes={store:C.default},I.propTypes={store:C.default},(0,x.default)(I,A)}}M.__esModule=!0;var n=Object.assign||function(A){for(var M=1;Mt;)g[t]=M[t++];return g},fA=function(A,M,t){G(A,M,{get:function(){return this._d[t]}})},mA=function(A){var M,t,I,g,e,i,T=y(A),E=arguments.length,n=E>1?arguments[1]:void 0,o=void 0!==n,c=Y(T);if(void 0!=c&&!j(c)){for(i=c.call(T),I=[],M=0;!(e=i.next()).done;M++)I.push(e.value);T=I}for(o&&E>2&&(n=N(n,arguments[2],2)),M=0,t=D(T.length),g=pA(this,t);t>M;M++)g[M]=o?n(T[M],M):T[M];return g},FA=function(){for(var A=0,M=arguments.length,t=pA(this,M);M>A;)t[A]=arguments[A++];return t},kA=!!X&&e(function(){BA.call(new X(1))}),RA=function(){return BA.apply(kA?DA.call(zA(this)):zA(this),arguments)},JA={copyWithin:function(A,M){return k.call(zA(this),A,M,arguments.length>2?arguments[2]:void 0)},every:function(A){return tA(zA(this),A,arguments.length>1?arguments[1]:void 0)},fill:function(A){return F.apply(zA(this),arguments)},filter:function(A){return UA(this,AA(zA(this),A,arguments.length>1?arguments[1]:void 0))},find:function(A){return IA(zA(this),A,arguments.length>1?arguments[1]:void 0)},findIndex:function(A){return gA(zA(this),A,arguments.length>1?arguments[1]:void 0)},forEach:function(A){$(zA(this),A,arguments.length>1?arguments[1]:void 0)},indexOf:function(A){return iA(zA(this),A,arguments.length>1?arguments[1]:void 0)},includes:function(A){return eA(zA(this),A,arguments.length>1?arguments[1]:void 0)},join:function(A){return CA.apply(zA(this),arguments)},lastIndexOf:function(A){return nA.apply(zA(this),arguments)},map:function(A){return LA(zA(this),A,arguments.length>1?arguments[1]:void 0)},reduce:function(A){return oA.apply(zA(this),arguments)},reduceRight:function(A){return cA.apply(zA(this),arguments)},reverse:function(){for(var A,M=this,t=zA(M).length,I=Math.floor(t/2),g=0;g1?arguments[1]:void 0)},sort:function(A){return aA.call(zA(this),A)},subarray:function(A,M){var t=zA(this),I=t.length,g=r(A,I);return new(p(t,t[xA]))(t.buffer,t.byteOffset+g*t.BYTES_PER_ELEMENT,D((void 0===M?I:r(M,I))-g))}},GA=function(A,M){return UA(this,DA.call(zA(this),A,M))},HA=function(A){zA(this);var M=SA(arguments[1],1),t=this.length,I=y(A),g=D(I.length),e=0;if(g+M>t)throw v(wA);for(;e255?255:255&I),g.v[a](t*M+g.o,I,YA)},h=function(A,M){G(A,M,{get:function(){return Y(this,M)},set:function(A){return d(this,M,A)},enumerable:!0})};s?(r=t(function(A,t,I,g){n(A,r,N,"_d");var e,i,T,E,o=0,C=0;if(x(t)){if(!(t instanceof q||(E=u(t))==W||E==V))return jA in t?OA(r,t):mA.call(r,t);e=t,C=SA(I,M);var a=t.byteLength;if(void 0===g){if(a%M)throw v(wA);if(i=a-C,i<0)throw v(wA)}else if(i=D(g)*M,i+C>a)throw v(wA);T=i/M}else T=hA(t,!0),i=T*M,e=new q(i);for(c(A,"_d",{b:e,o:C,l:i,e:T,v:new _(e)});o0?I:t)(A)}},function(A,M){"use strict";var t=function(A){var M;for(M in A)if(A.hasOwnProperty(M))return M;return null};A.exports=t},function(A,M,t){var I=t(120),g=t(85),e=t(73),i="[object Array]",T=Object.prototype,E=T.toString,N=I(Array,"isArray"),n=N||function(A){return e(A)&&g(A.length)&&E.call(A)==i};A.exports=n},function(A,M){function t(A){var M=typeof A;return!!A&&("object"==M||"function"==M)}A.exports=t},function(A,M,t){"use strict";function I(A){return A&&A.__esModule?A:{default:A}}function g(A){return null==A||c.default.isValidElement(A)}function e(A){return g(A)||Array.isArray(A)&&A.every(g)}function i(A,M){return n({},A,M)}function T(A){var M=A.type,t=i(M.defaultProps,A.props);if(t.children){var I=E(t.children,t);I.length&&(t.childRoutes=I),delete t.children}return t}function E(A,M){var t=[];return c.default.Children.forEach(A,function(A){if(c.default.isValidElement(A))if(A.type.createRouteFromReactElement){var I=A.type.createRouteFromReactElement(A,M);I&&t.push(I)}else t.push(T(A))}),t}function N(A){return e(A)?A=E(A):A&&!Array.isArray(A)&&(A=[A]),A}M.__esModule=!0;var n=Object.assign||function(A){for(var M=1;M";for(M.style.display="none",t(142).appendChild(M),M.src="javascript:",A=M.contentWindow.document,A.open(),A.write(g+"script"+i+"document.F=Object"+g+"/script"+i),A.close(),N=A.F;I--;)delete N[E][e[I]];return N()};A.exports=Object.create||function(A,M){var t;return null!==A?(T[E]=I(A),t=new T,T[E]=null,t[i]=A):t=N(),void 0===M?t:g(t,M)}},function(A,M,t){var I=t(233),g=t(140).concat("length","prototype");M.f=Object.getOwnPropertyNames||function(A){return I(A,g)}},function(A,M,t){var I=t(233),g=t(140);A.exports=Object.keys||function(A){return I(A,g)}},function(A,M,t){var I=t(26);A.exports=function(A,M,t){for(var g in M)I(A,g,M[g],t);return A}},function(A,M,t){"use strict";var I=t(5),g=t(14),e=t(13),i=t(11)("species");A.exports=function(A){var M=I[A];e&&M&&!M[i]&&g.f(M,i,{configurable:!0,get:function(){return this}})}},function(A,M,t){var I=t(57),g=Math.max,e=Math.min;A.exports=function(A,M){return A=I(A),A<0?g(A+M,0):e(A,M)}},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){"use strict";A.exports=!("undefined"==typeof window||!window.document||!window.document.createElement)},function(A,M){function t(A){return!!A&&"object"==typeof A}A.exports=t},function(A,M,t){"use strict";function I(A,M,t){if(A[M])return new Error("<"+t+'> 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(2),e=g.PropTypes.func,i=g.PropTypes.object,T=g.PropTypes.arrayOf,E=g.PropTypes.oneOfType,N=g.PropTypes.element,n=g.PropTypes.shape,o=g.PropTypes.string,c=(M.history=n({listen:e.isRequired,push:e.isRequired,replace:e.isRequired,go:e.isRequired,goBack:e.isRequired,goForward:e.isRequired}),M.component=E([e,o])),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(47);I(i)},function(A,M,t){"use strict";function I(){g.attachRefs(this,this._currentElement)}var g=t(751),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(62),e=t(8),i=t(44),T=(t(7),{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,t){var I=t(11)("unscopables"),g=Array.prototype;void 0==g[I]&&t(25)(g,I,{}),A.exports=function(A){g[I][A]=!0}},function(A,M,t){var I=t(49),g=t(227),e=t(144),i=t(4),T=t(17),E=t(161),N={},n={},M=A.exports=function(A,M,t,o,c){var C,a,D,r,B=c?function(){return A}:E(A),Q=I(t,o,M?2:1),s=0;if("function"!=typeof B)throw TypeError(A+" is not iterable!");if(e(B)){for(C=T(A.length);C>s;s++)if(r=M?Q(i(a=A[s])[0],a[1]):Q(A[s]),r===N||r===n)return r}else for(D=B.call(A);!(a=D.next()).done;)if(r=g(D,Q,a.value,M),r===N||r===n)return r};M.BREAK=N,M.RETURN=n},function(A,M){A.exports={}},function(A,M,t){var I=t(14).f,g=t(21),e=t(11)("toStringTag");A.exports=function(A,M,t){A&&!g(A=t?A:A.prototype,e)&&I(A,e,{configurable:!0,value:M})}},function(A,M,t){var I=t(1),g=t(36),e=t(6),i=t(157),T="["+i+"]",E="​…",N=RegExp("^"+T+T+"*"),n=RegExp(T+T+"*$"),o=function(A,M,t){var g={},T=e(function(){return!!i[A]()||E[A]()!=E}),N=g[A]=T?M(c):i[A];t&&(g[t]=N),I(I.P+I.F*T,"String",g)},c=o.trim=function(A,M){return A=String(g(A)),1&M&&(A=A.replace(N,"")),2&M&&(A=A.replace(n,"")),A};A.exports=o},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(72),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 DI.apply(null,arguments)}function t(A){DI=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 QI)I=QI[t],g=M[I],r(g)||(A[I]=g);return A}function Q(A){B(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 Q||null!=A&&null!=A._isAMomentObject}function u(A){return A<0?Math.ceil(A)||0:Math.floor(A)}function x(A){var M=+A,t=0;return 0!==M&&isFinite(M)&&(t=u(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 m(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?(v(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 v(A,M,t){A.isValid()&&A._d["set"+(A._isUTC?"UTC":"")+M](t)}function b(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&&(fI[A]=g),M&&(fI[M[0]]=function(){return W(g.apply(this,arguments),M[1],M[2])}),t&&(fI[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]=x(A)}),t=0;t=0&&isFinite(T.getFullYear())&&T.setFullYear(A),T}function uA(A){var M=new Date(Date.UTC.apply(null,arguments));return A<100&&A>=0&&isFinite(M.getUTCFullYear())&&M.setUTCFullYear(A),M}function xA(A,M,t){var I=7+M-t,g=(7+uA(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=xA(A,I,g),N=1+7*(M-1)+T+E;return N<=0?(e=A-1,i=rA(e)+N):N>rA(A)?(e=A+1,i=N-rA(A)):(e=A,i=N),{year:e,dayOfYear:i}}function jA(A,M,t){var I,g,e=xA(A.year(),M,t),i=Math.floor((A.dayOfYear()-e-1)/7)+1;return i<1?(g=A.year()-1,I=i+lA(g,M,t)):i>lA(A.year(),M,t)?(I=i-lA(A.year(),M,t),g=A.year()+1):(g=A.year(),I=i),{week:I,year:g}}function lA(A,M,t){var I=xA(A,M,t),g=xA(A+1,M,t);return(rA(A)-I+g)/7}function wA(A){return jA(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=jA(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 fA(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=o([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=ng.call(this._weekdaysParse,i),g!==-1?g:null):"ddd"===M?(g=ng.call(this._shortWeekdaysParse,i),g!==-1?g:null):(g=ng.call(this._minWeekdaysParse,i),g!==-1?g:null):"dddd"===M?(g=ng.call(this._weekdaysParse,i),g!==-1?g:(g=ng.call(this._shortWeekdaysParse,i),g!==-1?g:(g=ng.call(this._minWeekdaysParse,i),g!==-1?g:null))):"ddd"===M?(g=ng.call(this._shortWeekdaysParse,i),g!==-1?g:(g=ng.call(this._weekdaysParse,i),g!==-1?g:(g=ng.call(this._minWeekdaysParse,i),g!==-1?g:null))):(g=ng.call(this._minWeekdaysParse,i),g!==-1?g:(g=ng.call(this._weekdaysParse,i),g!==-1?g:(g=ng.call(this._shortWeekdaysParse,i),g!==-1?g:null)))}function mA(A,M,t){var I,g,e;if(this._weekdaysParseExact)return fA.call(this,A,M,t);for(this._weekdaysParse||(this._weekdaysParse=[],this._minWeekdaysParse=[],this._shortWeekdaysParse=[],this._fullWeekdaysParse=[]), +I=0;I<7;I++){if(g=o([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")||vA.call(this),A?this._weekdaysStrictRegex:this._weekdaysRegex):(N(this,"_weekdaysRegex")||(this._weekdaysRegex=xg),this._weekdaysStrictRegex&&A?this._weekdaysStrictRegex:this._weekdaysRegex)}function GA(A){return this._weekdaysParseExact?(N(this,"_weekdaysRegex")||vA.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")||vA.call(this),A?this._weekdaysMinStrictRegex:this._weekdaysMinRegex):(N(this,"_weekdaysMinRegex")||(this._weekdaysMinRegex=jg),this._weekdaysMinStrictRegex&&A?this._weekdaysMinStrictRegex:this._weekdaysMinRegex)}function vA(){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=o([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 bA(){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=lg._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=r(M)?tM(A):AM(A,M),t&&(lg=t)),lg._abbr}function AM(A,M){if(null!==M){var t=Yg;if(M.abbr=A,null!=dg[A])w("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 lg;if(!I(A)){if(M=_A(A))return M;A=[A]}return qA(A)}function IM(){return jI(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;MrA(g)&&(C(A)._overflowDayOfYear=!0),t=uA(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?uA:sA).apply(null,e),null!=A._tzm&&A._d.setUTCMinutes(A._d.getUTCMinutes()-A._tzm),A._nextDay&&(A._a[gg]=24)}}function nM(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],jA(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=jA(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>lA(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 oM(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),fI[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(!r(this._isDSTShifted))return this._isDSTShifted;var A={};if(B(A,this),A=rM(A),A._a){var M=A._isUTC?o(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 lM(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:x(T[Ig])*t,h:x(T[gg])*t,m:x(T[eg])*t,s:x(T[ig])*t,ms:x(wM(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=bM(sM(e.from),sM(e.to)),e={},e.ms=g.milliseconds,e.M=g.months),I=new jM(e),lM(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 vM(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 bM(A,M){var t;return A.isValid()&&M.isValid()?(M=dM(M,A),A.isBefore(M)?t=vM(A,M):(t=vM(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)||(w(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=wM(t._days),T=wM(t._months);A.isValid()&&(g=null==g||g,e&&A._d.setTime(A._d.valueOf()+e*I),i&&v(A,"Date",H(A,"Date")+i*I),T&&nA(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 Q(this)}function KM(A,M){var t=s(A)?A:sM(A);return!(!this.isValid()||!t.isValid())&&(M=F(r(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=uA(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 ft(A,M){M[Tg]=x(1e3*("0."+A))}function mt(){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=o().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 vt(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 bt(A,M){return Ht(A,M,"months")}function Xt(A,M){return Ht(A,M,"monthsShort")}function Wt(A,M,t){return vt(A,M,t,"weekdays")}function Vt(A,M,t){return vt(A,M,t,"weekdaysShort")}function Pt(A,M,t){return vt(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=u(e/1e3),E.seconds=A%60,M=u(A/60),E.minutes=M%60,t=u(M/60),E.hours=t%24,i+=u(t/24),g=u(MI(i)),T+=g,i-=$t(tI(g)),I=u(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*x(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 u(this.days()/7)}function NI(A,M,t,I,g){return g.relativeTime(M||1,!!t,A,I)}function nI(A,M,t){var I=GM(A).abs(),g=ae(I.as("s")),e=ae(I.as("m")),i=ae(I.as("h")),T=ae(I.as("d")),E=ae(I.as("M")),N=ae(I.as("y")),n=g0,n[4]=t,NI.apply(null,n)}function oI(A){return void 0===A?ae:"function"==typeof A&&(ae=A,!0)}function cI(A,M){return void 0!==De[A]&&(void 0===M?De[A]:(De[A]=M,!0))}function CI(A){var M=this.localeData(),t=nI(this,!A,M);return A&&(t=M.pastFuture(+this,t)),M.postformat(t)}function aI(){var A,M,t,I=re(this._milliseconds)/1e3,g=re(this._days),e=re(this._months);A=u(I/60),M=u(A/60),I%=60,A%=60,t=u(e/12),e%=12;var i=t,T=e,E=g,N=M,n=A,o=I,c=this.asSeconds();return c?(c<0?"-":"")+"P"+(i?i+"Y":"")+(T?T+"M":"")+(E?E+"D":"")+(N||n||o?"T":"")+(N?N+"H":"")+(n?n+"M":"")+(o?o+"S":""):"P0D"}var DI,rI;rI=Array.prototype.some?Array.prototype.some:function(A){for(var M=Object(this),t=M.length>>>0,I=0;I68?1900:2e3)};var rg=G("FullYear",!0);V("w",["ww",2],"wo","week"),V("W",["WW",2],"Wo","isoWeek"),m("week","w"),m("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)]=x(A)});var Bg={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"),m("day","d"),m("weekday","e"),m("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]=x(A)});var Qg="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),sg="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),ug="Su_Mo_Tu_We_Th_Fr_Sa".split("_"),xg=_I,yg=_I,jg=_I;V("H",["HH",2],0,"hour"),V("h",["hh",2],0,bA),V("k",["kk",2],0,XA),V("hmm",0,0,function(){return""+bA.apply(this)+W(this.minutes(),2)}),V("hmmss",0,0,function(){return""+bA.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),m("hour","h"),R("hour",13),_("a",VA),_("A",VA),_("H",GI),_("h",GI),_("HH",GI,FI),_("hh",GI,FI),_("hmm",HI),_("hmmss",vI),_("Hmm",HI),_("Hmmss",vI),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]=x(A),C(t).bigHour=!0}),tA("hmm",function(A,M,t){var I=A.length-2;M[gg]=x(A.substr(0,I)),M[eg]=x(A.substr(I)),C(t).bigHour=!0}),tA("hmmss",function(A,M,t){var I=A.length-4,g=A.length-2;M[gg]=x(A.substr(0,I)),M[eg]=x(A.substr(I,2)),M[ig]=x(A.substr(g)),C(t).bigHour=!0}),tA("Hmm",function(A,M,t){var I=A.length-2;M[gg]=x(A.substr(0,I)),M[eg]=x(A.substr(I))}),tA("Hmmss",function(A,M,t){var I=A.length-4,g=A.length-2;M[gg]=x(A.substr(0,I)),M[eg]=x(A.substr(I,2)),M[ig]=x(A.substr(g))});var lg,wg=/[ap]\.?m?\.?/i,Lg=G("Hours",!0),Yg={calendar:lI,longDateFormat:wI,invalidDate:LI,ordinal:YI,ordinalParse:dI,relativeTime:hI,months:cg,monthsShort:Cg,week:Bg,weekdays:Qg,weekdaysMin:ug,weekdaysShort:sg,meridiemParse:wg},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/]],fg=/^\/?Date\((\-?\d+)/i;M.createFromInputFallback=l("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 mg=l("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:D()}),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=jM.prototype;var Hg=XM(1,"add"),vg=XM(-1,"subtract");M.defaultFormat="YYYY-MM-DDTHH:mm:ssZ",M.defaultFormatUtc="YYYY-MM-DDTHH:mm:ss[Z]";var bg=l("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"),m("weekYear","gg"),m("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)]=x(A)}),IA(["gg","GG"],function(A,t,I,g){t[g]=M.parseTwoDigitYear(A)}),V("Q",0,"Qo","quarter"),m("quarter","Q"),R("quarter",7),_("Q",mI),tA("Q",function(A,M){M[tg]=3*(x(A)-1)}),V("D",["DD",2],"Do","date"),m("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]=x(A.match(GI)[0],10)});var Xg=G("Date",!0);V("DDD",["DDDD",3],"DDDo","dayOfYear"),m("dayOfYear","DDD"),R("dayOfYear",4),_("DDD",bI),_("DDDD",kI),tA(["DDD","DDDD"],function(A,M,t){t._dayOfYear=x(A)}),V("m",["mm",2],0,"minute"),m("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"),m("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()}),m("millisecond","ms"),R("millisecond",16),_("S",bI,mI),_("SS",bI,FI),_("SSS",bI,kI);var Pg;for(Pg="SSSS";Pg.length<=9;Pg+="S")_(Pg,VI);for(Pg="S";Pg.length<=9;Pg+="S")tA(Pg,ft);var Zg=G("Milliseconds",!1);V("z",0,0,"zoneAbbr"),V("zz",0,0,"zoneName");var Kg=Q.prototype;Kg.add=Hg,Kg.calendar=PM,Kg.clone=ZM,Kg.diff=tt,Kg.endOf=Dt,Kg.format=Tt,Kg.from=Et,Kg.fromNow=Nt,Kg.to=nt,Kg.toNow=ot,Kg.get=b,Kg.invalidAt=lt,Kg.isAfter=KM,Kg.isBefore=qM,Kg.isBetween=_M,Kg.isSame=$M,Kg.isSameOrAfter=At,Kg.isSameOrBefore=Mt,Kg.isValid=yt,Kg.lang=bg,Kg.locale=ct,Kg.localeData=Ct,Kg.max=Fg,Kg.min=mg,Kg.parsingFlags=jt,Kg.set=X,Kg.startOf=at,Kg.subtract=vg,Kg.toArray=st,Kg.toObject=ut,Kg.toDate=Qt,Kg.toISOString=et,Kg.inspect=it,Kg.toJSON=xt,Kg.toString=gt,Kg.unix=Bt,Kg.valueOf=rt,Kg.creationData=wt,Kg.year=rg,Kg.isLeapYear=QA,Kg.weekYear=Yt,Kg.isoWeekYear=dt,Kg.quarter=Kg.quarters=Ut,Kg.month=oA,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=fM,Kg.isDST=mM,Kg.isLocal=kM,Kg.isUtcOffset=RM,Kg.isUtc=JM,Kg.isUTC=JM,Kg.zoneAbbr=mt,Kg.zoneName=Ft,Kg.dates=l("dates accessor is deprecated. Use date instead.",Xg),Kg.months=l("months accessor is deprecated. Use month instead",oA),Kg.years=l("years accessor is deprecated. Use year instead",rg),Kg.zone=l("moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/",zM),Kg.isDSTShifted=l("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=f,qg.set=Y,qg.months=iA,qg.monthsShort=TA,qg.monthsParse=NA,qg.monthsRegex=aA,qg.monthsShortRegex=CA,qg.week=wA,qg.firstDayOfYear=YA,qg.firstDayOfWeek=LA,qg.weekdays=pA,qg.weekdaysMin=OA,qg.weekdaysShort=UA,qg.weekdaysParse=mA,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===x(A%100/10)?"th":1===M?"st":2===M?"nd":3===M?"rd":"th";return A+t}}),M.lang=l("moment.lang is deprecated. Use moment.locale instead.",$A),M.langData=l("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"),ne=TI("hours"),oe=TI("days"),ce=TI("months"),Ce=TI("years"),ae=Math.round,De={s:45,m:45,h:22,d:26,M:11},re=Math.abs,Be=jM.prototype;return Be.abs=Zt,Be.add=qt,Be.subtract=_t,Be.as=II,Be.asMilliseconds=$g,Be.asSeconds=Ae,Be.asMinutes=Me,Be.asHours=te,Be.asDays=Ie,Be.asWeeks=ge,Be.asMonths=ee,Be.asYears=ie,Be.valueOf=gI,Be._bubble=AI,Be.get=iI,Be.milliseconds=Te,Be.seconds=Ee,Be.minutes=Ne,Be.hours=ne,Be.days=oe,Be.weeks=EI,Be.months=ce,Be.years=Ce,Be.humanize=CI,Be.toISOString=aI,Be.toString=aI,Be.toJSON=aI,Be.locale=ct,Be.localeData=Ct,Be.toIsoString=l("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",aI),Be.lang=bg,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(x(A))}),M.version="2.17.1",t(sM),M.fn=Kg,M.min=xM,M.max=yM,M.now=kg,M.utc=o,M.unix=kt,M.months=bt,M.isDate=T,M.locale=$A,M.invalid=D,M.duration=GM,M.isMoment=s,M.weekdays=Wt,M.parseZone=Rt,M.localeData=tM,M.isDuration=lM,M.monthsShort=Xt,M.weekdaysMin=Pt,M.defineLocale=AM,M.updateLocale=MM,M.locales=IM,M.weekdaysShort=Vt,M.normalizeUnits=F,M.relativeTimeRounding=oI,M.relativeTimeThreshold=cI,M.calendarFormat=VM,M.prototype=Kg,M})}).call(M,t(215)(A))},function(A,M,t){"use strict";var I=t(284).default,g=t(285).default,e=t(180).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 n="default";M.DEFAULT=n;var o="primary";M.PRIMARY=o;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;t=0||Object.prototype.hasOwnProperty.call(A,I)&&(t[I]=A[I]);return t},M.__esModule=!0},function(A,M,t){"use strict";function I(A){return A&&A.__esModule?A:{default:A}}function g(A){return A.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function e(A){for(var M="",t=[],I=[],e=void 0,i=0,T=/:([a-zA-Z_$][a-zA-Z0-9_$]*)|\*\*|\*|\(|\)/g;e=T.exec(A);)e.index!==i&&(I.push(A.slice(i,e.index)),M+=g(A.slice(i,e.index))),e[1]?(M+="([^/]+)",t.push(e[1])):"**"===e[0]?(M+="(.*)",t.push("splat")):"*"===e[0]?(M+="(.*?)",t.push("splat")):"("===e[0]?M+="(?:":")"===e[0]&&(M+=")?"),I.push(e[0]),i=T.lastIndex;return i!==A.length&&(I.push(A.slice(i,A.length)),M+=g(A.slice(i,A.length))),{pattern:A,regexpSource:M,paramNames:t,tokens:I}}function i(A){return C[A]||(C[A]=e(A)),C[A]}function T(A,M){"/"!==A.charAt(0)&&(A="/"+A);var t=i(A),I=t.regexpSource,g=t.paramNames,e=t.tokens;"/"!==A.charAt(A.length-1)&&(I+="/?"),"*"===e[e.length-1]&&(I+="$");var T=M.match(new RegExp("^"+I,"i"));if(null==T)return null;var E=T[0],N=M.substr(E.length);if(N){if("/"!==E.charAt(E.length-1))return null;N="/"+N}return{remainingPathname:N,paramNames:g,paramValues:T.slice(1).map(function(A){return A&&decodeURIComponent(A)})}}function E(A){return i(A).paramNames}function N(A,M){var t=T(A,M);if(!t)return null;var I=t.paramNames,g=t.paramValues,e={};return I.forEach(function(A,M){e[A]=g[M]}),e}function n(A,M){M=M||{};for(var t=i(A),I=t.tokens,g=0,e="",T=0,E=void 0,N=void 0,n=void 0,o=0,C=I.length;o0?void 0:(0,c.default)(!1),null!=n&&(e+=encodeURI(n))):"("===E?g+=1:")"===E?g-=1:":"===E.charAt(0)?(N=E.substring(1),n=M[N],null!=n||g>0?void 0:(0,c.default)(!1),null!=n&&(e+=encodeURIComponent(n))):e+=E;return e.replace(/\/+/g,"/")}M.__esModule=!0,M.compilePattern=i,M.matchPattern=T,M.getParamNames=E,M.getParams=N,M.formatPattern=n;var o=t(16),c=I(o),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(3),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||{},n=A.DOMMutationMethods||{};A.isCustomAttribute&&T._isCustomAttributeFunctions.push(A.isCustomAttribute);for(var o in t){T.properties.hasOwnProperty(o)?g(!1):void 0;var c=o.toLowerCase(),C=t[o],a={attributeName:c,attributeNamespace:null,propertyName:o,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(a.mustUseAttribute&&a.mustUseProperty?g(!1):void 0,!a.mustUseProperty&&a.hasSideEffects?g(!1):void 0,a.hasBooleanValue+a.hasNumericValue+a.hasOverloadedBooleanValue<=1?void 0:g(!1),E.hasOwnProperty(o)){var D=E[o];a.attributeName=D}i.hasOwnProperty(o)&&(a.attributeNamespace=i[o]),N.hasOwnProperty(o)&&(a.propertyName=N[o]),n.hasOwnProperty(o)&&(a.mutationMethod=n[o]),T.properties[o]=a}}},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&&n(A,e,t,I,!1,!0),e!==M&&n(e,M,t,g,!0,!1)},traverseTwoPhase:function(A,M,t){A&&(n("",A,M,t,!0,!1),n(A,"",M,t,!1,!0))},traverseTwoPhaseSkipTarget:function(A,M,t){A&&(n("",A,M,t,!0,!0),n(A,"",M,t,!0,!0))},traverseAncestors:function(A,M,t){n("",A,M,t,!0,!1)},getFirstCommonAncestorID:N,_getNextDescendantID:E,isAncestorIDOf:i,SEPARATOR:C};A.exports=r},function(A,M,t){var I=t(35),g=t(11)("toStringTag"),e="Arguments"==I(function(){return arguments}()),i=function(A,M){try{return A[M]}catch(A){}};A.exports=function(A){var M,t,T;return void 0===A?"Undefined":null===A?"Null":"string"==typeof(t=i(M=Object(A),g))?t:e?I(M):"Object"==(T=I(M))&&"function"==typeof M.callee?"Arguments":T}},function(A,M,t){var I=t(35);A.exports=Object("z").propertyIsEnumerable(0)?Object:function(A){return"String"==I(A)?A.split(""):Object(A)}},function(A,M){M.f={}.propertyIsEnumerable},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 o.default.Children.map(A,function(A){if(o.default.isValidElement(A)){var g=I;return I++,M.call(t,A,g)}return A})}function g(A,M,t){var I=0;return o.default.Children.forEach(A,function(A){o.default.isValidElement(A)&&(M.call(t,A,I),I++)})}function e(A){var M=0;return o.default.Children.forEach(A,function(A){o.default.isValidElement(A)&&M++}),M}function i(A){var M=!1;return o.default.Children.forEach(A,function(A){!M&&o.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 o.default.Children.forEach(A,function(A){o.default.isValidElement(A)&&(M.call(t,A,I)&&g.push(A),I++)}),g}var N=t(12).default;M.__esModule=!0;var n=t(2),o=N(n);M.default={map:I,forEach:g,numberOf:e,find:T,findValidComponents:E,hasValidComponent:i},A.exports=M.default},function(A,M){var t=A.exports={version:"1.2.6"};"number"==typeof __e&&(__e=t)},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(34),e=I(g),i=t(83),T=I(i);A.exports=M.default},function(A,M,t){"use strict";var I=t(316),g=t(729),e=t(329),i=t(338),T=t(339),E=t(3),N=(t(7),{}),n=null,o=function(A,M){A&&(g.executeDispatchesInOrder(A,M),A.isPersistent()||A.constructor.release(A))},c=function(A){return o(A,!0)},C=function(A){return o(A,!1)},a=null,D={injection:{injectMount:g.injection.injectMount,injectInstanceHandle:function(A){a=A},getInstanceHandle:function(){return a},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;Nn;)if(T=E[n++],T!=T)return!0}else for(;N>n;n++)if((A||n in E)&&E[n]===t)return A||n||0;return!A&&-1}}},function(A,M,t){"use strict";var I=t(5),g=t(1),e=t(26),i=t(68),T=t(55),E=t(79),N=t(63),n=t(9),o=t(6),c=t(111),C=t(81),a=t(143);A.exports=function(A,M,t,D,r,B){var Q=I[A],s=Q,u=r?"set":"add",x=s&&s.prototype,y={},j=function(A){var M=x[A];e(x,A,"delete"==A?function(A){return!(B&&!n(A))&&M.call(this,0===A?0:A)}:"has"==A?function(A){return!(B&&!n(A))&&M.call(this,0===A?0:A)}:"get"==A?function(A){return B&&!n(A)?void 0:M.call(this,0===A?0:A)}:"add"==A?function(A){return M.call(this,0===A?0:A),this}:function(A,t){return M.call(this,0===A?0:A,t),this})};if("function"==typeof s&&(B||x.forEach&&!o(function(){(new s).entries().next()}))){var l=new s,w=l[u](B?{}:-0,1)!=l,L=o(function(){l.has(1)}),Y=c(function(A){new s(A)}),d=!B&&o(function(){for(var A=new s,M=5;M--;)A[u](M,M);return!A.has(-0)});Y||(s=M(function(M,t){N(M,s,A);var I=a(new Q,M,s);return void 0!=t&&E(t,r,I[u],I),I}),s.prototype=x,x.constructor=s),(L||d)&&(j("delete"),j("has"),r&&j("get")),(d||w)&&j(u),B&&x.clear&&delete x.clear}else s=D.getConstructor(M,A,r,u),i(s.prototype,t),T.NEED=!0;return C(s,A),y[A]=s,g(g.G+g.W+g.F*(s!=Q),y),B||D.setStrong(s,A,r),s}},function(A,M,t){"use strict";var I=t(25),g=t(26),e=t(6),i=t(36),T=t(11);A.exports=function(A,M,t){var E=T(A),N=t(i,E,""[A]),n=N[0],o=N[1];e(function(){var M={};return M[E]=function(){return 7},7!=""[A](M)})&&(g(String.prototype,A,n),I(RegExp.prototype,E,2==M?function(A,M){return o.call(A,this,M)}:function(A){return o.call(A,this)}))}},function(A,M,t){"use strict";var I=t(4);A.exports=function(){var A=I(this),M="";return A.global&&(M+="g"),A.ignoreCase&&(M+="i"),A.multiline&&(M+="m"),A.unicode&&(M+="u"),A.sticky&&(M+="y"),M}},function(A,M){A.exports=function(A,M,t){var I=void 0===t;switch(M.length){case 0:return I?A():A.call(t);case 1:return I?A(M[0]):A.call(t,M[0]);case 2:return I?A(M[0],M[1]):A.call(t,M[0],M[1]);case 3:return I?A(M[0],M[1],M[2]):A.call(t,M[0],M[1],M[2]);case 4:return I?A(M[0],M[1],M[2],M[3]):A.call(t,M[0],M[1],M[2],M[3])}return A.apply(t,M)}},function(A,M,t){var I=t(9),g=t(35),e=t(11)("match");A.exports=function(A){var M;return I(A)&&(void 0!==(M=A[e])?!!M:"RegExp"==g(A))}},function(A,M,t){var I=t(11)("iterator"),g=!1;try{var e=[7][I]();e.return=function(){g=!0},Array.from(e,function(){throw 2})}catch(A){}A.exports=function(A,M){if(!M&&!g)return!1;var t=!1;try{var e=[7],i=e[I]();i.next=function(){return{done:t=!0}},e[I]=function(){return i},A(e)}catch(A){}return t}},function(A,M,t){A.exports=t(64)||!t(6)(function(){var A=Math.random();__defineSetter__.call(null,A,function(){}),delete t(5)[A]})},function(A,M){M.f=Object.getOwnPropertySymbols},function(A,M,t){var I=t(5),g="__core-js_shared__",e=I[g]||(I[g]={});A.exports=function(A){return e[A]||(e[A]={})}},function(A,M,t){for(var I,g=t(5),e=t(25),i=t(71),T=i("typed_array"),E=i("view"),N=!(!g.ArrayBuffer||!g.DataView),n=N,o=0,c=9,C="Int8Array,Uint8Array,Uint8ClampedArray,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array".split(",");o1?I-1:0),e=1;e":">","<":"<",'"':""","'":"'"},e=/[&><"']/g;A.exports=I},function(A,M,t){"use strict";var I=t(20),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(2),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,n=A.readonly,o=A.autoComplete,c=A.align,C=A.className,a=e.default.createElement("input",{id:t,name:I,value:g,onChange:i,className:"ig-text",type:T,spellCheck:E,required:N,autoComplete:o});return n&&(a=e.default.createElement("input",{id:t,name:I,value:g,onChange:i,className:"ig-text",type:T,spellCheck:E,required:N,autoComplete:o,disabled:!0})),e.default.createElement("div",{className:"input-group "+c+" "+C},a,e.default.createElement("i",{className:"ig-helpers"}),e.default.createElement("label",{className:"ig-label"},M))};M.default=i},function(A,M,t){"use strict";var I=t(19),g=t(70),e=t(17);A.exports=function(A){for(var M=I(this),t=e(M.length),i=arguments.length,T=g(i>1?arguments[1]:void 0,t),E=i>2?arguments[2]:void 0,N=void 0===E?t:g(E,t);N>T;)M[T++]=A;return M}},function(A,M,t){"use strict";var I=t(14),g=t(56);A.exports=function(A,M,t){M in A?I.f(A,M,g(0,t)):A[M]=t}},function(A,M,t){var I=t(9),g=t(5).document,e=I(g)&&I(g.createElement);A.exports=function(A){return e?g.createElement(A):{}}},function(A,M){A.exports="constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split(",")},function(A,M,t){var I=t(11)("match");A.exports=function(A){var M=/./;try{"/./"[A](M)}catch(t){try{return M[I]=!1,!"/./"[A](M)}catch(A){}}return!0}},function(A,M,t){A.exports=t(5).document&&document.documentElement},function(A,M,t){var I=t(9),g=t(151).set;A.exports=function(A,M,t){var e,i=M.constructor;return i!==t&&"function"==typeof i&&(e=i.prototype)!==t.prototype&&I(e)&&g&&g(A,e),A}},function(A,M,t){var I=t(80),g=t(11)("iterator"),e=Array.prototype;A.exports=function(A){return void 0!==A&&(I.Array===A||e[g]===A)}},function(A,M,t){var I=t(35);A.exports=Array.isArray||function(A){return"Array"==I(A)}},function(A,M,t){"use strict";var I=t(65),g=t(56),e=t(81),i={};t(25)(i,t(11)("iterator"),function(){return this}),A.exports=function(A,M,t){A.prototype=I(i,{next:g(1,t)}),e(A,M+" Iterator")}},function(A,M,t){"use strict";var I=t(64),g=t(1),e=t(26),i=t(25),T=t(21),E=t(80),N=t(146),n=t(81),o=t(31),c=t(11)("iterator"),C=!([].keys&&"next"in[].keys()),a="@@iterator",D="keys",r="values",B=function(){return this};A.exports=function(A,M,t,Q,s,u,x){N(t,M,Q);var y,j,l,w=function(A){if(!C&&A in h)return h[A];switch(A){case D:return function(){return new t(this,A)};case r:return function(){return new t(this,A)}}return function(){return new t(this,A)}},L=M+" Iterator",Y=s==r,d=!1,h=A.prototype,S=h[c]||h[a]||s&&h[s],z=S||w(s),p=s?Y?w("entries"):z:void 0,U="Array"==M?h.entries||S:S;if(U&&(l=o(U.call(new A)),l!==Object.prototype&&(n(l,L,!0),I||T(l,c)||i(l,c,B))),Y&&S&&S.name!==r&&(d=!0,z=function(){return S.call(this)}),I&&!x||!C&&!d&&h[c]||i(h,c,z),E[M]=z,E[L]=B,s)if(y={values:Y?z:w(r),keys:u?z:w(D),entries:p},x)for(j in y)j in h||e(h,j,y[j]);else g(g.P+g.F*(C||d),M,y);return y}},function(A,M){var t=Math.expm1;A.exports=!t||t(10)>22025.465794806718||t(10)<22025.465794806718||t(-2e-17)!=-2e-17?function(A){return 0==(A=+A)?A:A>-1e-6&&A<1e-6?A+A*A/2:Math.exp(A)-1}:t},function(A,M){A.exports=Math.sign||function(A){return 0==(A=+A)||A!=A?A:A<0?-1:1}},function(A,M,t){var I=t(5),g=t(158).set,e=I.MutationObserver||I.WebKitMutationObserver,i=I.process,T=I.Promise,E="process"==t(35)(i);A.exports=function(){var A,M,t,N=function(){var I,g;for(E&&(I=i.domain)&&I.exit();A;){g=A.fn,A=A.next;try{g()}catch(I){throw A?t():M=void 0,I}}M=void 0,I&&I.enter()};if(E)t=function(){i.nextTick(N)};else if(e){var n=!0,o=document.createTextNode("");new e(N).observe(o,{characterData:!0}),t=function(){o.data=n=!n}}else if(T&&T.resolve){var c=T.resolve();t=function(){c.then(N)}}else t=function(){g.call(I,N)};return function(I){var g={fn:I,next:void 0};M&&(M.next=g),A||(A=g,t()),M=g}}},function(A,M,t){var I=t(9),g=t(4),e=function(A,M){if(g(A),!I(M)&&null!==M)throw TypeError(M+": can't set as prototype!")};A.exports={set:Object.setPrototypeOf||("__proto__"in{}?function(A,M,I){try{I=t(49)(Function.call,t(30).f(Object.prototype,"__proto__").set,2),I(A,[]),M=!(A instanceof Array)}catch(A){M=!0}return function(A,t){return e(A,t),M?A.__proto__=t:I(A,t),A}}({},!1):void 0),check:e}},function(A,M,t){var I=t(114)("keys"),g=t(71);A.exports=function(A){return I[A]||(I[A]=g(A))}},function(A,M,t){var I=t(4),g=t(24),e=t(11)("species");A.exports=function(A,M){var t,i=I(A).constructor;return void 0===i||void 0==(t=I(i)[e])?M:g(t)}},function(A,M,t){var I=t(57),g=t(36);A.exports=function(A){return function(M,t){var e,i,T=String(g(M)),E=I(t),N=T.length;return E<0||E>=N?A?"":void 0:(e=T.charCodeAt(E),e<55296||e>56319||E+1===N||(i=T.charCodeAt(E+1))<56320||i>57343?A?T.charAt(E):e:A?T.slice(E,E+2):(e-55296<<10)+(i-56320)+65536)}}},function(A,M,t){var I=t(110),g=t(36);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){"use strict";var I=t(57),g=t(36);A.exports=function(A){var M=String(g(this)),t="",e=I(A);if(e<0||e==1/0)throw RangeError("Count can't be negative");for(;e>0;(e>>>=1)&&(M+=M))1&e&&(t+=M);return t}},function(A,M){A.exports="\t\n\v\f\r   ᠎              \u2028\u2029\ufeff"},function(A,M,t){var I,g,e,i=t(49),T=t(109),E=t(142),N=t(139),n=t(5),o=n.process,c=n.setImmediate,C=n.clearImmediate,a=n.MessageChannel,D=0,r={},B="onreadystatechange",Q=function(){var A=+this;if(r.hasOwnProperty(A)){var M=r[A];delete r[A],M()}},s=function(A){Q.call(A.data)};c&&C||(c=function(A){for(var M=[],t=1;arguments.length>t;)M.push(arguments[t++]);return r[++D]=function(){T("function"==typeof A?A:Function(A),M)},I(D),D},C=function(A){delete r[A]},"process"==t(35)(o)?I=function(A){o.nextTick(i(Q,A,1))}:a?(g=new a,e=g.port2,g.port1.onmessage=s,I=i(e.postMessage,e,1)):n.addEventListener&&"function"==typeof postMessage&&!n.importScripts?(I=function(A){n.postMessage(A+"","*")},n.addEventListener("message",s,!1)):I=B in N("script")?function(A){E.appendChild(N("script"))[B]=function(){E.removeChild(this),Q.call(A)}}:function(A){setTimeout(i(Q,A,1),0)}),A.exports={set:c,clear:C}},function(A,M,t){"use strict";var I=t(5),g=t(13),e=t(64),i=t(115),T=t(25),E=t(68),N=t(6),n=t(63),o=t(57),c=t(17),C=t(66).f,a=t(14).f,D=t(137),r=t(81),B="ArrayBuffer",Q="DataView",s="prototype",u="Wrong length!",x="Wrong index!",y=I[B],j=I[Q],l=I.Math,w=I.RangeError,L=I.Infinity,Y=y,d=l.abs,h=l.pow,S=l.floor,z=l.log,p=l.LN2,U="buffer",O="byteLength",f="byteOffset",m=g?"_b":U,F=g?"_l":O,k=g?"_o":f,R=function(A,M,t){var I,g,e,i=Array(t),T=8*t-M-1,E=(1<>1,n=23===M?h(2,-24)-h(2,-77):0,o=0,c=A<0||0===A&&1/A<0?1:0;for(A=d(A),A!=A||A===L?(g=A!=A?1:0,I=E):(I=S(z(A)/p),A*(e=h(2,-I))<1&&(I--,e*=2),A+=I+N>=1?n/e:n*h(2,1-N),A*e>=2&&(I++,e/=2),I+N>=E?(g=0,I=E):I+N>=1?(g=(A*e-1)*h(2,M),I+=N):(g=A*h(2,N-1)*h(2,M),I=0));M>=8;i[o++]=255&g,g/=256,M-=8);for(I=I<0;i[o++]=255&I,I/=256,T-=8);return i[--o]|=128*c,i},J=function(A,M,t){var I,g=8*t-M-1,e=(1<>1,T=g-7,E=t-1,N=A[E--],n=127&N;for(N>>=7;T>0;n=256*n+A[E],E--,T-=8);for(I=n&(1<<-T)-1,n>>=-T,T+=M;T>0;I=256*I+A[E],E--,T-=8);if(0===n)n=1-i;else{if(n===e)return I?NaN:N?-L:L;I+=h(2,M),n-=i}return(N?-1:1)*I*h(2,n-M)},G=function(A){return A[3]<<24|A[2]<<16|A[1]<<8|A[0]},H=function(A){return[255&A]},v=function(A){return[255&A,A>>8&255]},b=function(A){return[255&A,A>>8&255,A>>16&255,A>>24&255]},X=function(A){return R(A,52,8)},W=function(A){return R(A,23,4)},V=function(A,M,t){a(A[s],M,{get:function(){return this[t]}})},P=function(A,M,t,I){var g=+t,e=o(g);if(g!=e||e<0||e+M>A[F])throw w(x);var i=A[m]._b,T=e+A[k],E=i.slice(T,T+M);return I?E:E.reverse()},Z=function(A,M,t,I,g,e){var i=+t,T=o(i);if(i!=T||T<0||T+M>A[F])throw w(x);for(var E=A[m]._b,N=T+A[k],n=I(+g),c=0;cAA;)(q=$[AA++])in y||T(y,q,Y[q]);e||(_.constructor=y)}var MA=new j(new y(2)),tA=j[s].setInt8;MA.setInt8(0,2147483648),MA.setInt8(1,2147483649),!MA.getInt8(0)&&MA.getInt8(1)||E(j[s],{setInt8:function(A,M){tA.call(this,A,M<<24>>24)},setUint8:function(A,M){tA.call(this,A,M<<24>>24)}},!0)}else y=function(A){var M=K(this,A);this._b=D.call(Array(M),0),this[F]=M},j=function(A,M,t){n(this,j,Q),n(A,y,Q);var I=A[F],g=o(M);if(g<0||g>I)throw w("Wrong offset!");if(t=void 0===t?I-g:c(t),g+t>I)throw w(u);this[m]=A,this[k]=g,this[F]=t},g&&(V(y,O,"_l"),V(j,U,"_b"),V(j,O,"_l"),V(j,f,"_o")),E(j[s],{getInt8:function(A){return P(this,1,A)[0]<<24>>24},getUint8:function(A){return P(this,1,A)[0]},getInt16:function(A){var M=P(this,2,A,arguments[1]);return(M[1]<<8|M[0])<<16>>16},getUint16:function(A){var M=P(this,2,A,arguments[1]);return M[1]<<8|M[0]},getInt32:function(A){return G(P(this,4,A,arguments[1]))},getUint32:function(A){return G(P(this,4,A,arguments[1]))>>>0},getFloat32:function(A){return J(P(this,4,A,arguments[1]),23,4)},getFloat64:function(A){return J(P(this,8,A,arguments[1]),52,8)},setInt8:function(A,M){Z(this,1,A,H,M)},setUint8:function(A,M){Z(this,1,A,H,M)},setInt16:function(A,M){Z(this,2,A,v,M,arguments[2])},setUint16:function(A,M){Z(this,2,A,v,M,arguments[2])},setInt32:function(A,M){Z(this,4,A,b,M,arguments[2])},setUint32:function(A,M){Z(this,4,A,b,M,arguments[2])},setFloat32:function(A,M){Z(this,4,A,W,M,arguments[2])},setFloat64:function(A,M){Z(this,8,A,X,M,arguments[2])}});r(y,B),r(j,Q),T(j[s],i.VIEW,!0),M[B]=y,M[Q]=j},function(A,M,t){var I=t(5),g=t(48),e=t(64),i=t(240),T=t(14).f;A.exports=function(A){var M=g.Symbol||(g.Symbol=e?{}:I.Symbol||{});"_"==A.charAt(0)||A in M||T(M,A,{value:i.f(A)})}},function(A,M,t){var I=t(94),g=t(11)("iterator"),e=t(80);A.exports=t(48).getIteratorMethod=function(A){if(void 0!=A)return A[g]||A["@@iterator"]||e[I(A)]}},function(A,M,t){"use strict";var I=t(78),g=t(228),e=t(80),i=t(28);A.exports=t(147)(Array,"Array",function(A,M){this._t=i(A),this._i=0,this._k=M},function(){var A=this._t,M=this._k,t=this._i++;return!A||t>=A.length?(this._t=void 0,g(1)):"keys"==M?g(0,t):"values"==M?g(0,A[t]):g(0,[t,A[t]])},"values"),e.Arguments=e.Array,I("keys"),I("values"),I("entries")},function(A,M,t){"use strict";var I=t(72),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(252),g=t(571),e=t(566),i=t(567),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 n in N)T.call(N,n)&&(N[n]||0===N[n]?E+=g(n)+":"+N[n]+";":i(A,g(n)));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(n.L()?i[n.n()]:e[n.n()])+n.j()-1},W:function(){var A=n.z()-n.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(n.n(),2,"0")},M:function(){return n.F().slice(0,3)},n:function(){return t.getMonth()+1},t:function(){return new Date(n.Y(),n.n(),0).getDate()},L:function(){return 1===new Date(n.Y(),1,29).getMonth()?1:0},o:function(){var A=n.n(),M=n.W();return n.Y()+(12===A&&M<9?-1:1===A&&M>9)},Y:function(){return t.getFullYear()},y:function(){return String(n.Y()).slice(-2)},a:function(){return t.getHours()>11?"pm":"am"},A:function(){return n.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 n.G()%12||12},G:function(){return t.getHours()},h:function(){return g.pad(n.g(),2,"0")},H:function(){return g.pad(n.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=n.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),n=12*E+parseInt(g.date("n",A),10),o=N-n;if(o<12&&o>-12)return o>=0?o+" months ago":"in "+-o+" 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,B=c.enumErrorProps&&(A===j||A instanceof Error),Q=c.enumPrototypes&&T(A);++I1)for(var t=1;tM.documentElement.clientHeight;return{modalStyles:{paddingRight:I&&!g?B.default():void 0,paddingLeft:!I&&g?B.default():void 0}}}});b.Body=z.default,b.Header=U.default,b.Title=f.default,b.Footer=F.default,b.Dialog=h.default,b.TRANSITION_DURATION=300,b.BACKDROP_TRANSITION_DURATION=150,M.default=C.bsSizes([D.Sizes.LARGE,D.Sizes.SMALL],C.bsClass("modal",b)),A.exports=M.default},function(A,M,t){"use strict";var I=t(33).default,g=t(32).default,e=t(89).default,i=t(15).default,T=t(12).default;M.__esModule=!0;var E=t(2),N=T(E),n=t(10),o=T(n),c=t(22),C=T(c),a=t(88),D=T(a),r=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=D.default(this.context.$bs_onModalHide,this.props.onHide);return N.default.createElement("div",i({},t,{className:o.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);r.propTypes={"aria-label":N.default.PropTypes.string,bsClass:N.default.PropTypes.string,closeButton:N.default.PropTypes.bool,onHide:N.default.PropTypes.func},r.contextTypes={$bs_onModalHide:N.default.PropTypes.func},r.defaultProps={"aria-label":"Close",closeButton:!1},M.default=c.bsClass("modal",r),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(15).default,e=t(180).default,i=t(12).default;M.__esModule=!0;var T=t(84),E=i(T),N=t(277),n=i(N),o=t(2),c=i(o),C=t(34),a=i(C),D=t(214),r=(i(D),t(654)),B=i(r),Q=t(88),s=i(Q),u=c.default.createClass({displayName:"OverlayTrigger",propTypes:g({},B.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(){a.default.unstable_renderSubtreeIntoContainer(this,this._overlay,this._mountNode)},componentWillUnmount:function(){a.default.unmountComponentAtNode(this._mountNode), +this._mountNode=null,clearTimeout(this._hoverShowDelay),clearTimeout(this._hoverHideDelay)},componentDidUpdate:function(){this._mountNode&&this.renderOverlay()},getOverlayTarget:function(){return a.default.findDOMNode(this)},getOverlay:function(){var A=g({},n.default(this.props,e(B.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=o.cloneElement(this.props.overlay,{placement:A.placement,container:A.container});return c.default.createElement(B.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)),o.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=u,A.exports=M.default},function(A,M,t){"use strict";var I=t(15).default,g=t(12).default;M.__esModule=!0;var e=t(2),i=g(e),T=t(10),E=g(T),N=t(22),n=g(N),o=t(296),c=g(o),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[n.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:n.default.prefix(this.props,"arrow"),style:g}),i.default.createElement("div",{className:n.default.prefix(this.props,"inner")},this.props.children))}});M.default=C,A.exports=M.default},function(A,M,t){A.exports={default:t(661),__esModule:!0}},function(A,M,t){var I=t(667),g=t(99),e=t(286),i="prototype",T=function(A,M,t){var E,N,n,o=A&T.F,c=A&T.G,C=A&T.S,a=A&T.P,D=A&T.B,r=A&T.W,B=c?g:g[M]||(g[M]={}),Q=c?I:C?I[M]:(I[M]||{})[i];c&&(t=M);for(E in t)N=!o&&Q&&E in Q,N&&E in B||(n=N?Q[E]:t[E],B[E]=c&&"function"!=typeof Q[E]?t[E]:D&&N?e(n,I):r&&Q[E]==n?function(A){var M=function(M){return this instanceof A?new A(M):A(M)};return M[i]=A[i],M}(n):a&&"function"==typeof n?e(Function.call,n):n,a&&((B[i]||(B[i]={}))[E]=n))};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";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(34),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(2),T=I(i),E=t(295),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(2),i=t(125),T=(g(i),t(74)),E=I(T),N=t(18),n=(g(N),e.PropTypes.func),o=e.PropTypes.object,c=e.PropTypes.shape,C=e.PropTypes.string,a=M.routerShape=c({push:n.isRequired,replace:n.isRequired,go:n.isRequired,goBack:n.isRequired,goForward:n.isRequired,setRouteLeaveHook:n.isRequired,isActive:n.isRequired}),D=M.locationShape=c({pathname:C.isRequired,search:C.isRequired,state:o,action:C.isRequired,key:C}),r=M.falsy=E.falsy,B=M.history=E.history,Q=M.location=D,s=M.component=E.component,u=M.components=E.components,x=M.route=E.route,y=(M.routes=E.routes,M.router=a),j={falsy:r,history:B,location:Q,component:s,components:u,route:x,router:y};M.default=j},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){u&&u.location===A?e(u,t):(0,r.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,a.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,n.runLeaveHooks)(e,s),e.filter(function(A){return E.indexOf(A)===-1}).forEach(D),(0,n.runChangeHooks)(T,s,A,function(M,g){return M||g?I(M,g):void(0,n.runEnterHooks)(E,A,t)})}function T(A){var M=arguments.length<=1||void 0===arguments[1]||arguments[1];return A.__id__||M&&(A.__id__=x++)}function E(A){return A.reduce(function(A,M){return A.push.apply(A,y[T(M)]),A},[])}function o(A,t){(0,r.default)(M,A,function(M,I){if(null==I)return void t();u=i({},I,{location:A});for(var g=E((0,N.default)(s,u).leaveRoutes),e=void 0,T=0,n=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 n}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(735),i=t(327),T=t(333),E=t(8),N=t(3),n=(t(7),function(){});E(n.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` + "`" + `, @@ -206,8 +206,8 @@ function I(A,M){if(!e.canUseDOM||M&&!("addEventListener"in document))return!1;va */ for(g=97;g<123;g++)t[String.fromCharCode(g)]=g-32;for(var g=48;g<58;g++)t[g-48]=g;for(g=1;g<13;g++)t["f"+g]=g+111;for(g=0;g<10;g++)t["numpad "+g]=g+96;var e=M.names=M.title={};for(g in t)e[t[g]]=g;for(var i in I)t[i]=I[i]},function(A,M){function t(A,M){if("function"!=typeof A)throw new TypeError(I);return M=g(void 0===M?A.length-1:+M||0,0),function(){for(var t=arguments,I=-1,e=g(t.length-M,0),i=Array(e);++I-1&&A%1==0&&AA.clientHeight}Object.defineProperty(M,"__esModule",{value:!0}),M.default=i;var T=t(116),E=I(T),N=t(83),n=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(2),T=I(i),E=t(295),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,n=Array(N>6?N-6:0),o=6;o=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:n.falsy,children:n.falsy},render:function(){(0,T.default)(!1)}});M.default=a,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(2),e=I(g),i=t(16),T=I(i),E=t(61),N=t(74),n=e.default.PropTypes,o=n.string,c=n.func,C=e.default.createClass({displayName:"Route",statics:{createRouteFromReactElement:E.createRouteFromReactElement},propTypes:{path:o,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===n)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(47),E=(I(T),"@@History/"),N=["QuotaExceededError","QUOTA_EXCEEDED_ERR"],n="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=o.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(726),e=t(332),i=t(38),T=t(135),E=t(208),N=t(3),n={dangerouslyReplaceNodeWithMarkup:g.dangerouslyReplaceNodeWithMarkup,updateTextContent:E,processUpdates:function(A,M){for(var t,i=null,n=null,o=0;o-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(3),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(u,"//")}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);B(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,r.thatReturnsArgument):null!=E&&(D.isValidElement(E)&&(E=D.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);B(A,E,N),T.release(N)}function n(A,M,t){if(null==A)return A;var I=[];return N(A,I,null,M,t),I}function o(A,M,t){return null}function c(A,M){return B(A,o,null)}function C(A){var M=[];return N(A,M,null,r.thatReturnsArgument),M}var a=t(62),D=t(29),r=t(44),B=t(210),Q=a.twoArgumentPooler,s=a.fourArgumentPooler,u=/\/(?!\/)/g;g.prototype.destructor=function(){this.func=null,this.context=null,this.count=0},a.addPoolingTo(g,Q),T.prototype.destructor=function(){this.result=null,this.keyPrefix=null,this.func=null,this.context=null,this.count=0},a.addPoolingTo(T,s);var x={forEach:i,map:n,mapIntoWithKeyPrefixInternal:N,count:c,toArray:C};A.exports=x},function(A,M,t){"use strict";function I(A,M){var t=y.hasOwnProperty(M)?y[M]:null;l.hasOwnProperty(M)&&(t!==u.OVERRIDE_BASE?r(!1):void 0),A.hasOwnProperty(M)&&(t!==u.DEFINE_MANY&&t!==u.DEFINE_MANY_MERGED?r(!1):void 0)}function g(A,M){if(M){"function"==typeof M?r(!1):void 0,c.isValidElement(M)?r(!1):void 0;var t=A.prototype;M.hasOwnProperty(s)&&j.mixins(A,M.mixins);for(var g in M)if(M.hasOwnProperty(g)&&g!==s){var e=M[g];if(I(t,g),j.hasOwnProperty(g))j[g](A,e);else{var i=y.hasOwnProperty(g),N=t.hasOwnProperty(g),n="function"==typeof e,o=n&&!i&&!N&&M.autobind!==!1;if(o)t.__reactAutoBindMap||(t.__reactAutoBindMap={}),t.__reactAutoBindMap[g]=e,t[g]=e;else if(N){var C=y[g];!i||C!==u.DEFINE_MANY_MERGED&&C!==u.DEFINE_MANY?r(!1):void 0,C===u.DEFINE_MANY_MERGED?t[g]=T(t[g],e):C===u.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 j;g?r(!1):void 0;var e=t in A;e?r(!1):void 0,A[t]=I}}}function i(A,M){A&&M&&"object"==typeof A&&"object"==typeof M?void 0:r(!1);for(var t in M)M.hasOwnProperty(t)&&(void 0!==A[t]?r(!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 n(A){for(var M in A.__reactAutoBindMap)if(A.__reactAutoBindMap.hasOwnProperty(M)){var t=A.__reactAutoBindMap[M];A[M]=N(A,t)}}var o=t(319),c=t(29),C=(t(130),t(129),t(334)),a=t(8),D=t(97),r=t(3),B=t(118),Q=t(58),s=(t(7),Q({mixins:null})),u=B({DEFINE_ONCE:null,DEFINE_MANY:null,OVERRIDE_BASE:null,DEFINE_MANY_MERGED:null}),x=[],y={mixins:u.DEFINE_MANY,statics:u.DEFINE_MANY,propTypes:u.DEFINE_MANY,contextTypes:u.DEFINE_MANY,childContextTypes:u.DEFINE_MANY,getDefaultProps:u.DEFINE_MANY_MERGED,getInitialState:u.DEFINE_MANY_MERGED,getChildContext:u.DEFINE_MANY_MERGED,render:u.DEFINE_ONCE,componentWillMount:u.DEFINE_MANY,componentDidMount:u.DEFINE_MANY,componentWillReceiveProps:u.DEFINE_MANY,shouldComponentUpdate:u.DEFINE_ONCE,componentWillUpdate:u.DEFINE_MANY,componentDidUpdate:u.DEFINE_MANY,componentWillUnmount:u.DEFINE_MANY,updateComponent:u.OVERRIDE_BASE},j={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=n},function(A,M,t){"use strict";function I(){this.reinitializeTransaction()}var g=t(39),e=t(132),i=t(8),T=t(44),E={initialize:T,close:function(){c.isBatchingUpdates=!1}},N={initialize:T,close:g.flushBatchedUpdates.bind(g)},n=[N,E];i(I.prototype,e.Mixin,{getTransactionWrappers:function(){return n}});var o=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):o.perform(A,null,M,t,I,g,e)}};A.exports=c},function(A,M,t){"use strict";function I(){if(!w){w=!0,B.EventEmitter.injectReactEventListener(r),B.EventPluginHub.injectEventPluginOrder(T),B.EventPluginHub.injectInstanceHandle(Q),B.EventPluginHub.injectMount(s),B.EventPluginHub.injectEventPluginsByName({SimpleEventPlugin:j,EnterLeaveEventPlugin:E,ChangeEventPlugin:e,SelectEventPlugin:x,BeforeInputEventPlugin:g}),B.NativeComponent.injectGenericComponentClass(a),B.NativeComponent.injectTextComponentClass(D),B.Class.injectMixin(o),B.DOMProperty.injectDOMPropertyConfig(n),B.DOMProperty.injectDOMPropertyConfig(l),B.EmptyComponent.injectEmptyComponent("noscript"),B.Updates.injectReconcileTransaction(u),B.Updates.injectBatchingStrategy(C),B.RootIndex.injectCreateReactRootIndex(N.canUseDOM?i.createReactRootIndex:y.createReactRootIndex),B.Component.injectEnvironment(c)}}var g=t(722),e=t(724),i=t(725),T=t(727),E=t(728),N=t(20),n=t(731),o=t(733),c=t(196),C=t(324),a=t(737),D=t(323),r=t(745),B=t(746),Q=t(93),s=t(23),u=t(750),x=t(756),y=t(757),j=t(758),l=t(755),w=!1;A.exports={inject:I}},function(A,M,t){"use strict";function I(){if(o.current){var A=o.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=a[A]||(a[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!==o.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(118),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=o[M];return null==t&&(o[M]=t=N(M)),t}function g(A){return n?void 0:E(!1),new n(A.type,A.props)}function e(A){return new c(A)}function i(A){return A instanceof c}var T=t(8),E=t(3),N=null,n=null,o={},c=null,C={injectGenericComponentClass:function(A){n=A},injectTextComponentClass:function(A){c=A},injectComponentClasses:function(A){T(o,A)}},a={getComponentClassForElement:I,createInternalComponent:g,createInstanceForText:e,isTextComponent:i,injection:C};A.exports=a},function(A,M,t){"use strict";function I(A,M){}var g=(t(7),{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=D(i);if(T!==A){var E=s[g],N=r(i);return new Error("Invalid "+E+" ` + "`" + `"+e+"` + "`" + ` of type "+("` + "`" + `"+N+"` + "`" + ` supplied to ` + "`" + `"+I+"` + "`" + `, expected ")+("` + "`" + `"+A+"` + "`" + `."))}return null}return I(M)}function e(){return I(u.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=D(i);return new Error("Invalid "+T+" ` + "`" + `"+e+"` + "`" + ` of type "+("` + "`" + `"+E+"` + "`" + ` supplied to ` + "`" + `"+I+"` + "`" + `, expected an array."))}for(var N=0;N>"}var Q=t(29),s=t(129),u=t(44),x=t(205),y="<>",j={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:n,oneOf:N,oneOfType:o,shape:C};A.exports=j},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(3);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(20),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=Q++;t=B||(B=T(M)),I=n.bind(null,t,e,!1),g=n.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=o.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 n(A,M,t,I){var g=t?"":I.css;if(A.styleSheet)A.styleSheet.cssText=u(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 o(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={},a=function(A){var M;return function(){return"undefined"==typeof M&&(M=A.apply(this,arguments)),M}},D=a(function(){return/msie [6-9]\b/.test(window.navigator.userAgent.toLowerCase())}),r=a(function(){return document.head||document.getElementsByTagName("head")[0]}),B=null,Q=0,s=[];A.exports=function(A,M){M=M||{},"undefined"==typeof M.singleton&&(M.singleton=D()),"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;a.default.push(TA.pathJoin(g,e))}else window.location=window.location.origin+"/minio/download/"+g+"/"+e+"?token="+aA.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=[];E=T.length>0?T.map(function(A){return""+g+A}):[i.object],t.RemoveObject({bucketname:e,objects:E}).then(function(){if(A.hideDeleteConfirmation(),T.length>0){for(var M=0;M0})},n.default.createElement("span",{className:"la-label"},n.default.createElement("i",{className:"fa fa-check-circle"})," ",C.length," Objects selected"),n.default.createElement("span",{className:"la-actions pull-right"},n.default.createElement("button",{onClick:this.downloadSelected.bind(this)}," Download all as zip ")),n.default.createElement("span",{className:"la-actions pull-right"},n.default.createElement("button",{onClick:this.showDeleteConfirmation.bind(this)}," Delete selected ")),n.default.createElement("i",{className:"la-close fa fa-times",onClick:this.clearSelected.bind(this)})),n.default.createElement(f.default,null,b,n.default.createElement("header",{className:"fe-header-mobile hidden-lg hidden-md"},n.default.createElement("div",{id:"feh-trigger",className:"feh-trigger "+(0,c.default)({"feht-toggled":y}),onClick:this.toggleSidebar.bind(this,!y)},n.default.createElement("div",{className:"feht-lines"},n.default.createElement("div",{className:"top"}),n.default.createElement("div",{className:"center"}),n.default.createElement("div",{className:"bottom"}))),n.default.createElement("img",{className:"mh-logo",src:IA.default,alt:""})),n.default.createElement("header",{className:"fe-header"},n.default.createElement(G.default,{selectPrefix:this.selectPrefix.bind(this)}),AA,n.default.createElement("ul",{className:"feh-actions"},n.default.createElement(v.default,null),K,_)),n.default.createElement("div",{className:"feb-container"},n.default.createElement("header",{className:"fesl-row","data-type":"folder"},n.default.createElement("div",{className:"fesl-item fesl-item-icon"}),n.default.createElement("div",{className:"fesl-item fesl-item-name",onClick:this.sortObjectsByName.bind(this),"data-sort":"name"},"Name",n.default.createElement("i",{className:(0,c.default)({"fesli-sort":!0,fa:!0,"fa-sort-alpha-desc":i,"fa-sort-alpha-asc":!i})})),n.default.createElement("div",{className:"fesl-item fesl-item-size",onClick:this.sortObjectsBySize.bind(this),"data-sort":"size"},"Size",n.default.createElement("i",{className:(0,c.default)({"fesli-sort":!0,fa:!0,"fa-sort-amount-desc":T,"fa-sort-amount-asc":!T})})),n.default.createElement("div",{className:"fesl-item fesl-item-modified",onClick:this.sortObjectsByDate.bind(this),"data-sort":"last-modified"},"Last Modified",n.default.createElement("i",{className:(0,c.default)({"fesli-sort":!0,fa:!0,"fa-sort-numeric-desc":E,"fa-sort-numeric-asc":!E})})),n.default.createElement("div",{className:"fesl-item fesl-item-actions"}))),n.default.createElement("div",{className:"feb-container"},n.default.createElement(rA.default,{loadMore:this.listObjects.bind(this),hasMore:J,useWindow:!0,initialLoad:!1},n.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})),n.default.createElement("div",{className:"text-center",style:{display:J&&S?"block":"none"}},n.default.createElement("span",null,"Loading..."))),n.default.createElement(X.default,null),eA,n.default.createElement(s.default,{className:"modal-create-bucket",bsSize:"small",animation:!1,show:g,onHide:this.hideMakeBucketModal.bind(this)},n.default.createElement("button",{className:"close close-alt",onClick:this.hideMakeBucketModal.bind(this)},n.default.createElement("span",null,"×")),n.default.createElement(x.default,null,n.default.createElement("form",{onSubmit:this.makeBucket.bind(this)},n.default.createElement("div",{className:"input-group"},n.default.createElement("input",{className:"ig-text",type:"text",ref:"makeBucketRef",placeholder:"Bucket Name",autoFocus:!0}),n.default.createElement("i",{className:"ig-helpers"}))))),n.default.createElement(s.default,{className:"modal-about modal-dark",animation:!1,show:N,onHide:this.hideAbout.bind(this)},n.default.createElement("button",{className:"close",onClick:this.hideAbout.bind(this)},n.default.createElement("span",null,"×")),n.default.createElement("div",{className:"ma-inner"},n.default.createElement("div",{className:"mai-item hidden-xs"},n.default.createElement("a",{href:"https://minio.io",target:"_blank"},n.default.createElement("img",{className:"maii-logo",src:IA.default,alt:""}))),n.default.createElement("div",{className:"mai-item"},n.default.createElement("ul",{className:"maii-list"},n.default.createElement("li",null,n.default.createElement("div",null,"Version"),n.default.createElement("small",null,D)),n.default.createElement("li",null,n.default.createElement("div",null,"Memory"),n.default.createElement("small",null,B)),n.default.createElement("li",null,n.default.createElement("div",null,"Platform"),n.default.createElement("small",null,Q)),n.default.createElement("li",null,n.default.createElement("div",null,"Runtime"),n.default.createElement("small",null,u)))))),n.default.createElement(s.default,{className:"modal-policy",animation:!1,show:o,onHide:this.hideBucketPolicy.bind(this)},n.default.createElement(j.default,null,"Bucket Policy (",S,")",n.default.createElement("button",{className:"close close-alt",onClick:this.hideBucketPolicy.bind(this)},n.default.createElement("span",null,"×"))),n.default.createElement("div",{className:"pm-body"},n.default.createElement(Z.default,{bucket:S}),d.map(function(A,M){return n.default.createElement(q.default,{key:M,prefix:A.prefix,policy:A.policy})}))),n.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)}),n.default.createElement(s.default,{show:U.show,animation:!1,onHide:this.hideShareObjectModal.bind(this),bsSize:"small"},n.default.createElement(j.default,null,"Share Object"),n.default.createElement(x.default,null,n.default.createElement("div",{className:"input-group copy-text"},n.default.createElement("label",null,"Shareable Link"),n.default.createElement("input",{type:"text",ref:"copyTextInput",readOnly:"readOnly",value:window.location.protocol+"//"+U.url,onClick:this.selectTexts.bind(this)})),n.default.createElement("div",{className:"input-group",style:{display:m.LoggedIn()?"block":"none"}},n.default.createElement("label",null,"Expires in"),n.default.createElement("div",{className:"set-expire"},n.default.createElement("div",{className:"set-expire-item"},n.default.createElement("i",{className:"set-expire-increase",onClick:this.handleExpireValue.bind(this,"expireDays",1,U.object)}),n.default.createElement("div",{className:"set-expire-title"},"Days"),n.default.createElement("div",{className:"set-expire-value"},n.default.createElement("input",{ref:"expireDays",type:"number",min:0,max:7,defaultValue:5})),n.default.createElement("i",{className:"set-expire-decrease",onClick:this.handleExpireValue.bind(this,"expireDays",-1,U.object)})),n.default.createElement("div",{className:"set-expire-item"},n.default.createElement("i",{className:"set-expire-increase",onClick:this.handleExpireValue.bind(this,"expireHours",1,U.object)}),n.default.createElement("div",{className:"set-expire-title"},"Hours"),n.default.createElement("div",{className:"set-expire-value"},n.default.createElement("input",{ref:"expireHours",type:"number",min:0,max:23,defaultValue:0})),n.default.createElement("i",{className:"set-expire-decrease",onClick:this.handleExpireValue.bind(this,"expireHours",-1,U.object)})),n.default.createElement("div",{className:"set-expire-item"},n.default.createElement("i",{className:"set-expire-increase",onClick:this.handleExpireValue.bind(this,"expireMins",1,U.object)}),n.default.createElement("div",{className:"set-expire-title"},"Minutes"),n.default.createElement("div",{className:"set-expire-value"},n.default.createElement("input",{ref:"expireMins",type:"number",min:0,max:59,defaultValue:0})),n.default.createElement("i",{className:"set-expire-decrease",onClick:this.handleExpireValue.bind(this,"expireMins",-1,U.object)}))))),n.default.createElement("div",{className:"modal-footer"},n.default.createElement(cA.default,{text:window.location.protocol+"//"+U.url,onCopy:this.showMessage.bind(this)},n.default.createElement("button",{className:"btn btn-success"},"Copy Link")),n.default.createElement("button",{className:"btn btn-link",onClick:this.hideShareObjectModal.bind(this)},"Cancel"))),H)))}}]),M}(n.default.Component);M.default=BA},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(2),e=I(g),i=t(46),T=I(i),E=t(175),N=I(E),n=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})(n)},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(2),e=I(g),i=t(46),T=I(i),E=t(179),N=I(E),n=t(178),o=I(n),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(o.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("/")||(B=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"})),Q))));var s="",u="";return c.indexOf(A.name)>-1&&(s=" fesl-row-selected",u=!0),e.default.createElement("div",{key:M,className:"fesl-row "+r+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:u,onChange:function(M){return o(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"},a),e.default.createElement("div",{className:"fesl-item fesl-item-modified"},D),e.default.createElement("div",{className:"fesl-item fesl-item-actions"},B))});return e.default.createElement("div",null,a)};M.default=(0,o.default)(function(A){return{objects:A.objects,currentPath:A.currentPath,loadPath:A.loadPath}})(a)},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(2),e=I(g),i=t(46),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.APPEND_OBJECTS:t.objects=[].concat(e(t.objects),e(M.objects)),t.marker=M.marker,t.istruncated=M.istruncated;break;case T.SET_OBJECTS:t.objects=[].concat(e(M.objects));break;case T.RESET_OBJECTS:t.objects=[],t.marker="",t.istruncated=!1;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;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;a.default.push(TA.pathJoin(g,e))}else window.location=window.location.origin+"/minio/download/"+g+"/"+e+"?token="+aA.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=[];E=T.length>0?T.map(function(A){return""+g+A}):[i.object],t.RemoveObject({bucketname:e,objects:E}).then(function(){if(A.hideDeleteConfirmation(),T.length>0){for(var M=0;M0})},n.default.createElement("span",{className:"la-label"},n.default.createElement("i",{className:"fa fa-check-circle"})," ",C.length," Objects selected"),n.default.createElement("span",{className:"la-actions pull-right"},n.default.createElement("button",{onClick:this.downloadSelected.bind(this)}," Download all as zip ")),n.default.createElement("span",{className:"la-actions pull-right"},n.default.createElement("button",{onClick:this.showDeleteConfirmation.bind(this)}," Delete selected ")),n.default.createElement("i",{className:"la-close fa fa-times",onClick:this.clearSelected.bind(this)})),n.default.createElement(f.default,null,b,n.default.createElement("header",{className:"fe-header-mobile hidden-lg hidden-md"},n.default.createElement("div",{id:"feh-trigger",className:"feh-trigger "+(0,c.default)({"feht-toggled":y}),onClick:this.toggleSidebar.bind(this,!y)},n.default.createElement("div",{className:"feht-lines"},n.default.createElement("div",{className:"top"}),n.default.createElement("div",{className:"center"}),n.default.createElement("div",{className:"bottom"}))),n.default.createElement("img",{className:"mh-logo",src:IA.default,alt:""})),n.default.createElement("header",{className:"fe-header"},n.default.createElement(G.default,{selectPrefix:this.selectPrefix.bind(this)}),AA,n.default.createElement("ul",{className:"feh-actions"},n.default.createElement(v.default,null),K,_)),n.default.createElement("div",{className:"feb-container"},n.default.createElement("header",{className:"fesl-row","data-type":"folder"},n.default.createElement("div",{className:"fesl-item fesl-item-icon"}),n.default.createElement("div",{className:"fesl-item fesl-item-name",onClick:this.sortObjectsByName.bind(this),"data-sort":"name"},"Name",n.default.createElement("i",{className:(0,c.default)({"fesli-sort":!0,fa:!0,"fa-sort-alpha-desc":i,"fa-sort-alpha-asc":!i})})),n.default.createElement("div",{className:"fesl-item fesl-item-size",onClick:this.sortObjectsBySize.bind(this),"data-sort":"size"},"Size",n.default.createElement("i",{className:(0,c.default)({"fesli-sort":!0,fa:!0,"fa-sort-amount-desc":T,"fa-sort-amount-asc":!T})})),n.default.createElement("div",{className:"fesl-item fesl-item-modified",onClick:this.sortObjectsByDate.bind(this),"data-sort":"last-modified"},"Last Modified",n.default.createElement("i",{className:(0,c.default)({"fesli-sort":!0,fa:!0,"fa-sort-numeric-desc":E,"fa-sort-numeric-asc":!E})})),n.default.createElement("div",{className:"fesl-item fesl-item-actions"}))),n.default.createElement("div",{className:"feb-container"},n.default.createElement(rA.default,{loadMore:this.listObjects.bind(this),hasMore:J,useWindow:!0,initialLoad:!1},n.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})),n.default.createElement("div",{className:"text-center",style:{display:J&&S?"block":"none"}},n.default.createElement("span",null,"Loading..."))),n.default.createElement(X.default,null),eA,n.default.createElement(s.default,{className:"modal-create-bucket",bsSize:"small",animation:!1,show:g,onHide:this.hideMakeBucketModal.bind(this)},n.default.createElement("button",{className:"close close-alt",onClick:this.hideMakeBucketModal.bind(this)},n.default.createElement("span",null,"×")),n.default.createElement(x.default,null,n.default.createElement("form",{onSubmit:this.makeBucket.bind(this)},n.default.createElement("div",{className:"input-group"},n.default.createElement("input",{className:"ig-text",type:"text",ref:"makeBucketRef",placeholder:"Bucket Name",autoFocus:!0}),n.default.createElement("i",{className:"ig-helpers"}))))),n.default.createElement(s.default,{className:"modal-about modal-dark",animation:!1,show:N,onHide:this.hideAbout.bind(this)},n.default.createElement("button",{className:"close",onClick:this.hideAbout.bind(this)},n.default.createElement("span",null,"×")),n.default.createElement("div",{className:"ma-inner"},n.default.createElement("div",{className:"mai-item hidden-xs"},n.default.createElement("a",{href:"https://minio.io",target:"_blank"},n.default.createElement("img",{className:"maii-logo",src:IA.default,alt:""}))),n.default.createElement("div",{className:"mai-item"},n.default.createElement("ul",{className:"maii-list"},n.default.createElement("li",null,n.default.createElement("div",null,"Version"),n.default.createElement("small",null,D)),n.default.createElement("li",null,n.default.createElement("div",null,"Memory"),n.default.createElement("small",null,B)),n.default.createElement("li",null,n.default.createElement("div",null,"Platform"),n.default.createElement("small",null,Q)),n.default.createElement("li",null,n.default.createElement("div",null,"Runtime"),n.default.createElement("small",null,u)))))),n.default.createElement(s.default,{className:"modal-policy",animation:!1,show:o,onHide:this.hideBucketPolicy.bind(this)},n.default.createElement(j.default,null,"Bucket Policy (",S,")",n.default.createElement("button",{className:"close close-alt",onClick:this.hideBucketPolicy.bind(this)},n.default.createElement("span",null,"×"))),n.default.createElement("div",{className:"pm-body"},n.default.createElement(Z.default,{bucket:S}),d.map(function(A,M){return n.default.createElement(q.default,{key:M,prefix:A.prefix,policy:A.policy})}))),n.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)}),n.default.createElement(s.default,{show:U.show,animation:!1,onHide:this.hideShareObjectModal.bind(this),bsSize:"small"},n.default.createElement(j.default,null,"Share Object"),n.default.createElement(x.default,null,n.default.createElement("div",{className:"input-group copy-text"},n.default.createElement("label",null,"Shareable Link"),n.default.createElement("input",{type:"text",ref:"copyTextInput",readOnly:"readOnly",value:window.location.protocol+"//"+U.url,onClick:this.selectTexts.bind(this)})),n.default.createElement("div",{className:"input-group",style:{display:m.LoggedIn()?"block":"none"}},n.default.createElement("label",null,"Expires in (Max 7 days)"),n.default.createElement("div",{className:"set-expire"},n.default.createElement("div",{className:"set-expire-item"},n.default.createElement("i",{className:"set-expire-increase",onClick:this.handleExpireValue.bind(this,"expireDays",1,U.object)}),n.default.createElement("div",{className:"set-expire-title"},"Days"),n.default.createElement("div",{className:"set-expire-value"},n.default.createElement("input",{ref:"expireDays",type:"number",min:0,max:7,defaultValue:5,readOnly:"readOnly"})),n.default.createElement("i",{className:"set-expire-decrease",onClick:this.handleExpireValue.bind(this,"expireDays",-1,U.object)})),n.default.createElement("div",{className:"set-expire-item"},n.default.createElement("i",{className:"set-expire-increase",onClick:this.handleExpireValue.bind(this,"expireHours",1,U.object)}),n.default.createElement("div",{className:"set-expire-title"},"Hours"),n.default.createElement("div",{className:"set-expire-value"},n.default.createElement("input",{ref:"expireHours",type:"number",min:0,max:23,defaultValue:0,readOnly:"readOnly"})),n.default.createElement("i",{className:"set-expire-decrease",onClick:this.handleExpireValue.bind(this,"expireHours",-1,U.object)})),n.default.createElement("div",{className:"set-expire-item"},n.default.createElement("i",{className:"set-expire-increase",onClick:this.handleExpireValue.bind(this,"expireMins",1,U.object)}),n.default.createElement("div",{className:"set-expire-title"},"Minutes"),n.default.createElement("div",{className:"set-expire-value"},n.default.createElement("input",{ref:"expireMins",type:"number",min:0,max:59,defaultValue:0,readOnly:"readOnly"})),n.default.createElement("i",{className:"set-expire-decrease",onClick:this.handleExpireValue.bind(this,"expireMins",-1,U.object)}))))),n.default.createElement("div",{className:"modal-footer"},n.default.createElement(cA.default,{text:window.location.protocol+"//"+U.url,onCopy:this.showMessage.bind(this)},n.default.createElement("button",{className:"btn btn-success"},"Copy Link")),n.default.createElement("button",{className:"btn btn-link",onClick:this.hideShareObjectModal.bind(this)},"Cancel"))),H)))}}]),M}(n.default.Component);M.default=BA},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(2),e=I(g),i=t(46),T=I(i),E=t(175),N=I(E),n=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})(n)},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(2),e=I(g),i=t(46),T=I(i),E=t(179),N=I(E),n=t(178),o=I(n),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(o.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("/")||(B=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"})),Q))));var s="",u="";return c.indexOf(A.name)>-1&&(s=" fesl-row-selected",u=!0),e.default.createElement("div",{key:M,className:"fesl-row "+r+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:u,onChange:function(M){return o(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"},a),e.default.createElement("div",{className:"fesl-item fesl-item-modified"},D),e.default.createElement("div",{className:"fesl-item fesl-item-actions"},B))});return e.default.createElement("div",null,a)};M.default=(0,o.default)(function(A){return{objects:A.objects,currentPath:A.currentPath,loadPath:A.loadPath}})(a)},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(2),e=I(g),i=t(46),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.APPEND_OBJECTS:t.objects=[].concat(e(t.objects),e(M.objects)),t.marker=M.marker,t.istruncated=M.istruncated;break;case T.SET_OBJECTS:t.objects=[].concat(e(M.objects));break;case T.RESET_OBJECTS:t.objects=[],t.marker="",t.istruncated=!1;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;tN;)E.call(A,i=T[N++])&&M.push(i);return M}},function(A,M,t){var I=t(67),g=t(28);A.exports=function(A,M){for(var t,e=g(A),i=I(e),T=i.length,E=0;T>E;)if(e[t=i[E++]]===M)return t}},function(A,M,t){"use strict";var I=t(378),g=t(109),e=t(24);A.exports=function(){for(var A=e(this),M=arguments.length,t=Array(M),i=0,T=I._,E=!1;M>i;)(t[i]=arguments[i++])===T&&(E=!0);return function(){var I,e=this,i=arguments.length,N=0,n=0;if(!E&&!i)return g(A,t,e);if(I=t.slice(),E)for(;M>N;N++)I[N]===T&&(I[N]=arguments[n++]);for(;i>n;)I.push(arguments[n++]);return g(A,I,e)}}},function(A,M,t){A.exports=t(5)},function(A,M){A.exports=function(A,M){var t=M===Object(M)?function(A){return M[A]}:M;return function(M){return String(M).replace(A,t)}}},function(A,M,t){var I=t(1),g=t(379)(/[\\^$*+?.()|[\]{}]/g,"\\$&");I(I.S,"RegExp",{escape:function(A){return g(A)}})},function(A,M,t){var I=t(1);I(I.P,"Array",{copyWithin:t(218)}),t(78)("copyWithin")},function(A,M,t){"use strict";var I=t(1),g=t(41)(4);I(I.P+I.F*!t(37)([].every,!0),"Array",{every:function(A){return g(this,A,arguments[1])}})},function(A,M,t){var I=t(1);I(I.P,"Array",{fill:t(137)}),t(78)("fill")},function(A,M,t){"use strict";var I=t(1),g=t(41)(2);I(I.P+I.F*!t(37)([].filter,!0),"Array",{filter:function(A){return g(this,A,arguments[1])}})},function(A,M,t){"use strict";var I=t(1),g=t(41)(6),e="findIndex",i=!0;e in[]&&Array(1)[e](function(){i=!1}),I(I.P+I.F*i,"Array",{findIndex:function(A){return g(this,A,arguments.length>1?arguments[1]:void 0)}}),t(78)(e)},function(A,M,t){"use strict";var I=t(1),g=t(41)(5),e="find",i=!0;e in[]&&Array(1)[e](function(){i=!1}),I(I.P+I.F*i,"Array",{find:function(A){return g(this,A,arguments.length>1?arguments[1]:void 0)}}),t(78)(e)},function(A,M,t){"use strict";var I=t(1),g=t(41)(0),e=t(37)([].forEach,!0);I(I.P+I.F*!e,"Array",{forEach:function(A){return g(this,A,arguments[1])}})},function(A,M,t){"use strict";var I=t(49),g=t(1),e=t(19),i=t(227),T=t(144),E=t(17),N=t(138),n=t(161);g(g.S+g.F*!t(111)(function(A){Array.from(A)}),"Array",{from:function(A){var M,t,g,o,c=e(A),C="function"==typeof this?this:Array,a=arguments.length,D=a>1?arguments[1]:void 0,r=void 0!==D,B=0,Q=n(c);if(r&&(D=I(D,a>2?arguments[2]:void 0,2)),void 0==Q||C==Array&&T(Q))for(M=E(c.length),t=new C(M);M>B;B++)N(t,B,r?D(c[B],B):c[B]);else for(o=Q.call(c),t=new C;!(g=o.next()).done;B++)N(t,B,r?i(o,D,[g.value,B],!0):g.value);return t.length=B,t}})},function(A,M,t){"use strict";var I=t(1),g=t(105)(!1),e=[].indexOf,i=!!e&&1/[1].indexOf(1,-0)<0;I(I.P+I.F*(i||!t(37)(e)),"Array",{indexOf:function(A){return i?e.apply(this,arguments)||0:g(this,A,arguments[1])}})},function(A,M,t){var I=t(1);I(I.S,"Array",{isArray:t(145)})},function(A,M,t){"use strict";var I=t(1),g=t(28),e=[].join;I(I.P+I.F*(t(95)!=Object||!t(37)(e)),"Array",{join:function(A){return e.call(g(this),void 0===A?",":A)}})},function(A,M,t){"use strict";var I=t(1),g=t(28),e=t(57),i=t(17),T=[].lastIndexOf,E=!!T&&1/[1].lastIndexOf(1,-0)<0;I(I.P+I.F*(E||!t(37)(T)),"Array",{lastIndexOf:function(A){if(E)return T.apply(this,arguments)||0;var M=g(this),t=i(M.length),I=t-1;for(arguments.length>1&&(I=Math.min(I,e(arguments[1]))),I<0&&(I=t+I);I>=0;I--)if(I in M&&M[I]===A)return I||0;return-1}})},function(A,M,t){"use strict";var I=t(1),g=t(41)(1);I(I.P+I.F*!t(37)([].map,!0),"Array",{map:function(A){return g(this,A,arguments[1])}})},function(A,M,t){"use strict";var I=t(1),g=t(138);I(I.S+I.F*t(6)(function(){function A(){}return!(Array.of.call(A)instanceof A)}),"Array",{of:function(){for(var A=0,M=arguments.length,t=new("function"==typeof this?this:Array)(M);M>A;)g(t,A,arguments[A++]);return t.length=M,t}})},function(A,M,t){"use strict";var I=t(1),g=t(220);I(I.P+I.F*!t(37)([].reduceRight,!0),"Array",{reduceRight:function(A){return g(this,A,arguments.length,arguments[1],!0)}})},function(A,M,t){"use strict";var I=t(1),g=t(220);I(I.P+I.F*!t(37)([].reduce,!0),"Array",{reduce:function(A){return g(this,A,arguments.length,arguments[1],!1)}})},function(A,M,t){"use strict";var I=t(1),g=t(142),e=t(35),i=t(70),T=t(17),E=[].slice;I(I.P+I.F*t(6)(function(){g&&E.call(g)}),"Array",{slice:function(A,M){var t=T(this.length),I=e(this);if(M=void 0===M?t:M,"Array"==I)return E.call(this,A,M);for(var g=i(A,t),N=i(M,t),n=T(N-g),o=Array(n),c=0;c9?A:"0"+A};I(I.P+I.F*(g(function(){return"0385-07-25T07:06:39.999Z"!=new Date(-5e13-1).toISOString()})||!g(function(){new Date(NaN).toISOString()})),"Date",{toISOString:function(){if(!isFinite(e.call(this)))throw RangeError("Invalid time value");var A=this,M=A.getUTCFullYear(),t=A.getUTCMilliseconds(),I=M<0?"-":M>9999?"+":"";return I+("00000"+Math.abs(M)).slice(I?-6:-4)+"-"+i(A.getUTCMonth()+1)+"-"+i(A.getUTCDate())+"T"+i(A.getUTCHours())+":"+i(A.getUTCMinutes())+":"+i(A.getUTCSeconds())+"."+(t>99?t:"0"+i(t))+"Z"}})},function(A,M,t){"use strict";var I=t(1),g=t(19),e=t(43);I(I.P+I.F*t(6)(function(){return null!==new Date(NaN).toJSON()||1!==Date.prototype.toJSON.call({toISOString:function(){return 1}})}),"Date",{toJSON:function(A){var M=g(this),t=e(M);return"number"!=typeof t||isFinite(t)?M.toISOString():null}})},function(A,M,t){var I=t(11)("toPrimitive"),g=Date.prototype;I in g||t(25)(g,I,t(374))},function(A,M,t){var I=Date.prototype,g="Invalid Date",e="toString",i=I[e],T=I.getTime;new Date(NaN)+""!=g&&t(26)(I,e,function(){var A=T.call(this);return A===A?i.call(this):g})},function(A,M,t){var I=t(1);I(I.P,"Function",{bind:t(221)})},function(A,M,t){"use strict";var I=t(9),g=t(31),e=t(11)("hasInstance"),i=Function.prototype;e in i||t(14).f(i,e,{value:function(A){if("function"!=typeof this||!I(A))return!1;if(!I(this.prototype))return A instanceof this;for(;A=g(A);)if(this.prototype===A)return!0;return!1}})},function(A,M,t){var I=t(14).f,g=t(56),e=t(21),i=Function.prototype,T=/^\s*function ([^ (]*)/,E="name",N=Object.isExtensible||function(){return!0};E in i||t(13)&&I(i,E,{configurable:!0,get:function(){try{var A=this,M=(""+A).match(T)[1];return e(A,E)||!N(A)||I(A,E,g(5,M)),M}catch(A){return""}}})},function(A,M,t){var I=t(1),g=t(229),e=Math.sqrt,i=Math.acosh;I(I.S+I.F*!(i&&710==Math.floor(i(Number.MAX_VALUE))&&i(1/0)==1/0),"Math",{acosh:function(A){return(A=+A)<1?NaN:A>94906265.62425156?Math.log(A)+Math.LN2:g(A-1+e(A-1)*e(A+1))}})},function(A,M,t){function I(A){return isFinite(A=+A)&&0!=A?A<0?-I(-A):Math.log(A+Math.sqrt(A*A+1)):A}var g=t(1),e=Math.asinh;g(g.S+g.F*!(e&&1/e(0)>0),"Math",{asinh:I})},function(A,M,t){var I=t(1),g=Math.atanh;I(I.S+I.F*!(g&&1/g(-0)<0),"Math",{atanh:function(A){return 0==(A=+A)?A:Math.log((1+A)/(1-A))/2}})},function(A,M,t){var I=t(1),g=t(149);I(I.S,"Math",{cbrt:function(A){return g(A=+A)*Math.pow(Math.abs(A),1/3)}})},function(A,M,t){var I=t(1);I(I.S,"Math",{clz32:function(A){return(A>>>=0)?31-Math.floor(Math.log(A+.5)*Math.LOG2E):32}})},function(A,M,t){var I=t(1),g=Math.exp;I(I.S,"Math",{cosh:function(A){return(g(A=+A)+g(-A))/2}})},function(A,M,t){var I=t(1),g=t(148);I(I.S+I.F*(g!=Math.expm1),"Math",{expm1:g})},function(A,M,t){var I=t(1),g=t(149),e=Math.pow,i=e(2,-52),T=e(2,-23),E=e(2,127)*(2-T),N=e(2,-126),n=function(A){return A+1/i-1/i};I(I.S,"Math",{fround:function(A){var M,t,I=Math.abs(A),e=g(A);return IE||t!=t?e*(1/0):e*t)}})},function(A,M,t){var I=t(1),g=Math.abs;I(I.S,"Math",{hypot:function(A,M){for(var t,I,e=0,i=0,T=arguments.length,E=0;i0?(I=t/E,e+=I*I):e+=t;return E===1/0?1/0:E*Math.sqrt(e)}})},function(A,M,t){var I=t(1),g=Math.imul;I(I.S+I.F*t(6)(function(){return g(4294967295,5)!=-5||2!=g.length}),"Math",{imul:function(A,M){var t=65535,I=+A,g=+M,e=t&I,i=t&g;return 0|e*i+((t&I>>>16)*i+e*(t&g>>>16)<<16>>>0)}})},function(A,M,t){var I=t(1);I(I.S,"Math",{log10:function(A){return Math.log(A)/Math.LN10}})},function(A,M,t){var I=t(1);I(I.S,"Math",{log1p:t(229)})},function(A,M,t){var I=t(1);I(I.S,"Math",{log2:function(A){return Math.log(A)/Math.LN2}})},function(A,M,t){var I=t(1);I(I.S,"Math",{sign:t(149)})},function(A,M,t){var I=t(1),g=t(148),e=Math.exp;I(I.S+I.F*t(6)(function(){return!Math.sinh(-2e-17)!=-2e-17}),"Math",{sinh:function(A){return Math.abs(A=+A)<1?(g(A)-g(-A))/2:(e(A-1)-e(-A-1))*(Math.E/2)}})},function(A,M,t){var I=t(1),g=t(148),e=Math.exp;I(I.S,"Math",{tanh:function(A){var M=g(A=+A),t=g(-A);return M==1/0?1:t==1/0?-1:(M-t)/(e(A)+e(-A))}})},function(A,M,t){var I=t(1);I(I.S,"Math",{trunc:function(A){return(A>0?Math.floor:Math.ceil)(A)}})},function(A,M,t){"use strict";var I=t(5),g=t(21),e=t(35),i=t(143),T=t(43),E=t(6),N=t(66).f,n=t(30).f,o=t(14).f,c=t(82).trim,C="Number",a=I[C],D=a,r=a.prototype,B=e(t(65)(r))==C,Q="trim"in String.prototype,s=function(A){var M=T(A,!1);if("string"==typeof M&&M.length>2){M=Q?M.trim():c(M,3);var t,I,g,e=M.charCodeAt(0);if(43===e||45===e){if(t=M.charCodeAt(2),88===t||120===t)return NaN}else if(48===e){switch(M.charCodeAt(1)){case 66:case 98:I=2,g=49;break;case 79:case 111:I=8,g=55;break;default:return+M}for(var i,E=M.slice(2),N=0,n=E.length;Ng)return NaN;return parseInt(E,I)}}return+M};if(!a(" 0o1")||!a("0b1")||a("+0x1")){a=function(A){var M=arguments.length<1?0:A,t=this;return t instanceof a&&(B?E(function(){r.valueOf.call(t)}):e(t)!=C)?i(new D(s(M)),t,a):s(M)};for(var u,x=t(13)?N(D):"MAX_VALUE,MIN_VALUE,NaN,NEGATIVE_INFINITY,POSITIVE_INFINITY,EPSILON,isFinite,isInteger,isNaN,isSafeInteger,MAX_SAFE_INTEGER,MIN_SAFE_INTEGER,parseFloat,parseInt,isInteger".split(","),y=0;x.length>y;y++)g(D,u=x[y])&&!g(a,u)&&o(a,u,n(D,u));a.prototype=r,r.constructor=a,t(26)(I,C,a)}},function(A,M,t){var I=t(1);I(I.S,"Number",{EPSILON:Math.pow(2,-52)})},function(A,M,t){var I=t(1),g=t(5).isFinite;I(I.S,"Number",{isFinite:function(A){return"number"==typeof A&&g(A)}})},function(A,M,t){var I=t(1);I(I.S,"Number",{isInteger:t(226)})},function(A,M,t){var I=t(1);I(I.S,"Number",{isNaN:function(A){return A!=A}})},function(A,M,t){var I=t(1),g=t(226),e=Math.abs;I(I.S,"Number",{isSafeInteger:function(A){return g(A)&&e(A)<=9007199254740991}})},function(A,M,t){var I=t(1);I(I.S,"Number",{MAX_SAFE_INTEGER:9007199254740991})},function(A,M,t){var I=t(1);I(I.S,"Number",{MIN_SAFE_INTEGER:-9007199254740991})},function(A,M,t){var I=t(1),g=t(236);I(I.S+I.F*(Number.parseFloat!=g),"Number",{parseFloat:g})},function(A,M,t){var I=t(1),g=t(237);I(I.S+I.F*(Number.parseInt!=g),"Number",{parseInt:g})},function(A,M,t){"use strict";var I=t(1),g=t(57),e=t(217),i=t(156),T=1..toFixed,E=Math.floor,N=[0,0,0,0,0,0],n="Number.toFixed: incorrect invocation!",o="0",c=function(A,M){for(var t=-1,I=M;++t<6;)I+=A*N[t],N[t]=I%1e7,I=E(I/1e7)},C=function(A){for(var M=6,t=0;--M>=0;)t+=N[M],N[M]=E(t/A),t=t%A*1e7},a=function(){for(var A=6,M="";--A>=0;)if(""!==M||0===A||0!==N[A]){var t=String(N[A]);M=""===M?t:M+i.call(o,7-t.length)+t}return M},D=function(A,M,t){return 0===M?t:M%2===1?D(A,M-1,t*A):D(A*A,M/2,t)},r=function(A){for(var M=0,t=A;t>=4096;)M+=12,t/=4096;for(;t>=2;)M+=1,t/=2;return M};I(I.P+I.F*(!!T&&("0.000"!==8e-5.toFixed(3)||"1"!==.9.toFixed(0)||"1.25"!==1.255.toFixed(2)||"1000000000000000128"!==(0xde0b6b3a7640080).toFixed(0))||!t(6)(function(){T.call({})})),"Number",{toFixed:function(A){var M,t,I,T,E=e(this,n),N=g(A),B="",Q=o;if(N<0||N>20)throw RangeError(n);if(E!=E)return"NaN";if(E<=-1e21||E>=1e21)return String(E);if(E<0&&(B="-",E=-E),E>1e-21)if(M=r(E*D(2,69,1))-69,t=M<0?E*D(2,-M,1):E/D(2,M,1),t*=4503599627370496,M=52-M,M>0){for(c(0,t),I=N;I>=7;)c(1e7,0),I-=7;for(c(D(10,I,1),0),I=M-1;I>=23;)C(1<<23),I-=23;C(1<0?(T=Q.length,Q=B+(T<=N?"0."+i.call(o,N-T)+Q:Q.slice(0,T-N)+"."+Q.slice(T-N))):Q=B+Q,Q}})},function(A,M,t){"use strict";var I=t(1),g=t(6),e=t(217),i=1..toPrecision;I(I.P+I.F*(g(function(){return"1"!==i.call(1,void 0)})||!g(function(){i.call({})})),"Number",{toPrecision:function(A){var M=e(this,"Number#toPrecision: incorrect invocation!");return void 0===A?i.call(M):i.call(M,A)}})},function(A,M,t){var I=t(1);I(I.S+I.F,"Object",{assign:t(230)})},function(A,M,t){var I=t(1);I(I.S,"Object",{create:t(65)})},function(A,M,t){var I=t(1);I(I.S+I.F*!t(13),"Object",{defineProperties:t(231)})},function(A,M,t){var I=t(1);I(I.S+I.F*!t(13),"Object",{defineProperty:t(14).f})},function(A,M,t){var I=t(9),g=t(55).onFreeze;t(42)("freeze",function(A){return function(M){return A&&I(M)?A(g(M)):M}})},function(A,M,t){var I=t(28),g=t(30).f;t(42)("getOwnPropertyDescriptor",function(){return function(A,M){return g(I(A),M)}})},function(A,M,t){t(42)("getOwnPropertyNames",function(){return t(232).f})},function(A,M,t){var I=t(19),g=t(31);t(42)("getPrototypeOf",function(){return function(A){return g(I(A))}})},function(A,M,t){var I=t(9);t(42)("isExtensible",function(A){return function(M){return!!I(M)&&(!A||A(M))}})},function(A,M,t){var I=t(9);t(42)("isFrozen",function(A){return function(M){return!I(M)||!!A&&A(M)}})},function(A,M,t){var I=t(9);t(42)("isSealed",function(A){return function(M){return!I(M)||!!A&&A(M)}})},function(A,M,t){var I=t(1);I(I.S,"Object",{is:t(238)})},function(A,M,t){var I=t(19),g=t(67);t(42)("keys",function(){return function(A){return g(I(A))}})},function(A,M,t){var I=t(9),g=t(55).onFreeze;t(42)("preventExtensions",function(A){return function(M){return A&&I(M)?A(g(M)):M}})},function(A,M,t){var I=t(9),g=t(55).onFreeze;t(42)("seal",function(A){return function(M){return A&&I(M)?A(g(M)):M}})},function(A,M,t){var I=t(1);I(I.S,"Object",{setPrototypeOf:t(151).set})},function(A,M,t){"use strict";var I=t(94),g={};g[t(11)("toStringTag")]="z",g+""!="[object z]"&&t(26)(Object.prototype,"toString",function(){return"[object "+I(this)+"]"},!0)},function(A,M,t){var I=t(1),g=t(236);I(I.G+I.F*(parseFloat!=g),{parseFloat:g})},function(A,M,t){var I=t(1),g=t(237);I(I.G+I.F*(parseInt!=g),{parseInt:g})},function(A,M,t){"use strict";var I,g,e,i=t(64),T=t(5),E=t(49),N=t(94),n=t(1),o=t(9),c=t(24),C=t(63),a=t(79),D=t(153),r=t(158).set,B=t(150)(),Q="Promise",s=T.TypeError,u=T.process,x=T[Q],u=T.process,y="process"==N(u),j=function(){},l=!!function(){try{var A=x.resolve(1),M=(A.constructor={})[t(11)("species")]=function(A){A(j,j)};return(y||"function"==typeof PromiseRejectionEvent)&&A.then(j)instanceof M}catch(A){}}(),w=function(A,M){return A===M||A===x&&M===e},L=function(A){var M;return!(!o(A)||"function"!=typeof(M=A.then))&&M},Y=function(A){return w(x,A)?new d(A):new g(A)},d=g=function(A){var M,t;this.promise=new A(function(A,I){if(void 0!==M||void 0!==t)throw s("Bad Promise constructor");M=A,t=I}),this.resolve=c(M),this.reject=c(t)},h=function(A){try{A()}catch(A){return{error:A}}},S=function(A,M){if(!A._n){A._n=!0;var t=A._c;B(function(){for(var I=A._v,g=1==A._s,e=0,i=function(M){var t,e,i=g?M.ok:M.fail,T=M.resolve,E=M.reject,N=M.domain;try{i?(g||(2==A._h&&U(A),A._h=1),i===!0?t=I:(N&&N.enter(),t=i(I),N&&N.exit()),t===M.promise?E(s("Promise-chain cycle")):(e=L(t))?e.call(t,T,E):T(t)):E(I)}catch(A){E(A)}};t.length>e;)i(t[e++]);A._c=[],A._n=!1,M&&!A._h&&z(A)})}},z=function(A){r.call(T,function(){var M,t,I,g=A._v;if(p(A)&&(M=h(function(){y?u.emit("unhandledRejection",g,A):(t=T.onunhandledrejection)?t({promise:A,reason:g}):(I=T.console)&&I.error&&I.error("Unhandled promise rejection",g)}),A._h=y||p(A)?2:1),A._a=void 0,M)throw M.error})},p=function(A){if(1==A._h)return!1;for(var M,t=A._a||A._c,I=0;t.length>I;)if(M=t[I++],M.fail||!p(M.promise))return!1;return!0},U=function(A){r.call(T,function(){var M;y?u.emit("rejectionHandled",A):(M=T.onrejectionhandled)&&M({promise:A,reason:A._v})})},O=function(A){var M=this;M._d||(M._d=!0,M=M._w||M,M._v=A,M._s=2,M._a||(M._a=M._c.slice()),S(M,!0))},f=function(A){var M,t=this;if(!t._d){t._d=!0,t=t._w||t;try{if(t===A)throw s("Promise can't be resolved itself");(M=L(A))?B(function(){var I={_w:t,_d:!1};try{M.call(A,E(f,I,1),E(O,I,1))}catch(A){O.call(I,A)}}):(t._v=A,t._s=1,S(t,!1))}catch(A){O.call({_w:t,_d:!1},A)}}};l||(x=function(A){C(this,x,Q,"_h"),c(A),I.call(this);try{A(E(f,this,1),E(O,this,1))}catch(A){O.call(this,A)}},I=function(A){this._c=[],this._a=void 0,this._s=0,this._d=!1,this._v=void 0,this._h=0,this._n=!1},I.prototype=t(68)(x.prototype,{then:function(A,M){var t=Y(D(this,x));return t.ok="function"!=typeof A||A,t.fail="function"==typeof M&&M,t.domain=y?u.domain:void 0,this._c.push(t),this._a&&this._a.push(t),this._s&&S(this,!1),t.promise},catch:function(A){return this.then(void 0,A)}}),d=function(){var A=new I;this.promise=A,this.resolve=E(f,A,1),this.reject=E(O,A,1)}),n(n.G+n.W+n.F*!l,{Promise:x}),t(81)(x,Q),t(69)(Q),e=t(48)[Q],n(n.S+n.F*!l,Q,{reject:function(A){var M=Y(this),t=M.reject;return t(A),M.promise}}),n(n.S+n.F*(i||!l),Q,{resolve:function(A){if(A instanceof x&&w(A.constructor,this))return A;var M=Y(this),t=M.resolve;return t(A),M.promise}}),n(n.S+n.F*!(l&&t(111)(function(A){x.all(A).catch(j)})),Q,{all:function(A){var M=this,t=Y(M),I=t.resolve,g=t.reject,e=h(function(){var t=[],e=0,i=1;a(A,!1,function(A){var T=e++,E=!1;t.push(void 0),i++,M.resolve(A).then(function(A){E||(E=!0,t[T]=A,--i||I(t))},g)}),--i||I(t)});return e&&g(e.error),t.promise},race:function(A){var M=this,t=Y(M),I=t.reject,g=h(function(){a(A,!1,function(A){M.resolve(A).then(t.resolve,I)})});return g&&I(g.error),t.promise}})},function(A,M,t){var I=t(1),g=t(24),e=t(4),i=(t(5).Reflect||{}).apply,T=Function.apply;I(I.S+I.F*!t(6)(function(){i(function(){})}),"Reflect",{apply:function(A,M,t){var I=g(A),E=e(t);return i?i(I,M,E):T.call(I,M,E)}})},function(A,M,t){var I=t(1),g=t(65),e=t(24),i=t(4),T=t(9),E=t(6),N=t(221),n=(t(5).Reflect||{}).construct,o=E(function(){function A(){}return!(n(function(){},[],A)instanceof A)}),c=!E(function(){n(function(){})});I(I.S+I.F*(o||c),"Reflect",{construct:function(A,M){e(A),i(M);var t=arguments.length<3?A:e(arguments[2]);if(c&&!o)return n(A,M,t);if(A==t){switch(M.length){case 0:return new A;case 1:return new A(M[0]);case 2:return new A(M[0],M[1]);case 3:return new A(M[0],M[1],M[2]);case 4:return new A(M[0],M[1],M[2],M[3])}var I=[null];return I.push.apply(I,M),new(N.apply(A,I))}var E=t.prototype,C=g(T(E)?E:Object.prototype),a=Function.apply.call(A,C,M);return T(a)?a:C}})},function(A,M,t){var I=t(14),g=t(1),e=t(4),i=t(43);g(g.S+g.F*t(6)(function(){Reflect.defineProperty(I.f({},1,{value:1}),1,{value:2})}),"Reflect",{defineProperty:function(A,M,t){e(A),M=i(M,!0),e(t);try{return I.f(A,M,t),!0}catch(A){return!1}}})},function(A,M,t){var I=t(1),g=t(30).f,e=t(4);I(I.S,"Reflect",{deleteProperty:function(A,M){var t=g(e(A),M);return!(t&&!t.configurable)&&delete A[M]}})},function(A,M,t){"use strict";var I=t(1),g=t(4),e=function(A){this._t=g(A),this._i=0;var M,t=this._k=[];for(M in A)t.push(M)};t(146)(e,"Object",function(){var A,M=this,t=M._k;do if(M._i>=t.length)return{value:void 0,done:!0};while(!((A=t[M._i++])in M._t));return{value:A,done:!1}}),I(I.S,"Reflect",{enumerate:function(A){return new e(A)}})},function(A,M,t){var I=t(30),g=t(1),e=t(4);g(g.S,"Reflect",{getOwnPropertyDescriptor:function(A,M){return I.f(e(A),M)}})},function(A,M,t){var I=t(1),g=t(31),e=t(4);I(I.S,"Reflect",{getPrototypeOf:function(A){return g(e(A))}})},function(A,M,t){function I(A,M){var t,T,n=arguments.length<3?A:arguments[2];return N(A)===n?A[M]:(t=g.f(A,M))?i(t,"value")?t.value:void 0!==t.get?t.get.call(n):void 0:E(T=e(A))?I(T,M,n):void 0}var g=t(30),e=t(31),i=t(21),T=t(1),E=t(9),N=t(4);T(T.S,"Reflect",{get:I})},function(A,M,t){var I=t(1);I(I.S,"Reflect",{has:function(A,M){return M in A}})},function(A,M,t){var I=t(1),g=t(4),e=Object.isExtensible;I(I.S,"Reflect",{isExtensible:function(A){return g(A),!e||e(A)}})},function(A,M,t){var I=t(1);I(I.S,"Reflect",{ownKeys:t(235)})},function(A,M,t){var I=t(1),g=t(4),e=Object.preventExtensions;I(I.S,"Reflect",{preventExtensions:function(A){g(A);try{return e&&e(A),!0}catch(A){return!1}}})},function(A,M,t){var I=t(1),g=t(151);g&&I(I.S,"Reflect",{setPrototypeOf:function(A,M){g.check(A,M);try{return g.set(A,M),!0}catch(A){return!1}}})},function(A,M,t){function I(A,M,t){var E,c,C=arguments.length<4?A:arguments[3],a=e.f(n(A),M);if(!a){if(o(c=i(A)))return I(c,M,t,C);a=N(0)}return T(a,"value")?!(a.writable===!1||!o(C))&&(E=e.f(C,M)||N(0),E.value=t,g.f(C,M,E),!0):void 0!==a.set&&(a.set.call(C,t),!0)}var g=t(14),e=t(30),i=t(31),T=t(21),E=t(1),N=t(56),n=t(4),o=t(9);E(E.S,"Reflect",{set:I})},function(A,M,t){var I=t(5),g=t(143),e=t(14).f,i=t(66).f,T=t(110),E=t(108),N=I.RegExp,n=N,o=N.prototype,c=/a/g,C=/a/g,a=new N(c)!==c;if(t(13)&&(!a||t(6)(function(){return C[t(11)("match")]=!1,N(c)!=c||N(C)==C||"/a/i"!=N(c,"i")}))){N=function(A,M){var t=this instanceof N,I=T(A),e=void 0===M;return!t&&I&&A.constructor===N&&e?A:g(a?new n(I&&!e?A.source:A,M):n((I=A instanceof N)?A.source:A,I&&e?E.call(A):M),t?this:o,N)};for(var D=(function(A){A in N||e(N,A,{configurable:!0,get:function(){return n[A]},set:function(M){n[A]=M}})}),r=i(n),B=0;r.length>B;)D(r[B++]);o.constructor=N,N.prototype=o,t(26)(I,"RegExp",N)}t(69)("RegExp")},function(A,M,t){t(107)("match",1,function(A,M,t){return[function(t){"use strict";var I=A(this),g=void 0==t?void 0:t[M];return void 0!==g?g.call(t,I):new RegExp(t)[M](String(I))},t]})},function(A,M,t){t(107)("replace",2,function(A,M,t){return[function(I,g){"use strict";var e=A(this),i=void 0==I?void 0:I[M];return void 0!==i?i.call(I,e,g):t.call(String(e),I,g)},t]})},function(A,M,t){t(107)("search",1,function(A,M,t){return[function(t){"use strict";var I=A(this),g=void 0==t?void 0:t[M];return void 0!==g?g.call(t,I):new RegExp(t)[M](String(I))},t]})},function(A,M,t){t(107)("split",2,function(A,M,I){"use strict";var g=t(110),e=I,i=[].push,T="split",E="length",N="lastIndex";if("c"=="abbc"[T](/(b)*/)[1]||4!="test"[T](/(?:)/,-1)[E]||2!="ab"[T](/(?:ab)*/)[E]||4!="."[T](/(.?)(.?)/)[E]||"."[T](/()()/)[E]>1||""[T](/.?/)[E]){var n=void 0===/()??/.exec("")[1];I=function(A,M){var t=String(this);if(void 0===A&&0===M)return[];if(!g(A))return e.call(t,A,M);var I,T,o,c,C,a=[],D=(A.ignoreCase?"i":"")+(A.multiline?"m":"")+(A.unicode?"u":"")+(A.sticky?"y":""),r=0,B=void 0===M?4294967295:M>>>0,Q=new RegExp(A.source,D+"g");for(n||(I=new RegExp("^"+Q.source+"$(?!\\s)",D));(T=Q.exec(t))&&(o=T.index+T[0][E],!(o>r&&(a.push(t.slice(r,T.index)),!n&&T[E]>1&&T[0].replace(I,function(){for(C=1;C1&&T.index=B)));)Q[N]===T.index&&Q[N]++;return r===t[E]?!c&&Q.test("")||a.push(""):a.push(t.slice(r)),a[E]>B?a.slice(0,B):a}}else"0"[T](void 0,0)[E]&&(I=function(A,M){return void 0===A&&0===M?[]:e.call(this,A,M)});return[function(t,g){var e=A(this),i=void 0==t?void 0:t[M];return void 0!==i?i.call(t,e,g):I.call(String(e),t,g)},I]})},function(A,M,t){"use strict";t(242);var I=t(4),g=t(108),e=t(13),i="toString",T=/./[i],E=function(A){t(26)(RegExp.prototype,i,A,!0)};t(6)(function(){return"/a/b"!=T.call({source:"a",flags:"b"})})?E(function(){var A=I(this);return"/".concat(A.source,"/","flags"in A?A.flags:!e&&A instanceof RegExp?g.call(A):void 0)}):T.name!=i&&E(function(){return T.call(this)})},function(A,M,t){"use strict";t(27)("anchor",function(A){return function(M){return A(this,"a","name",M)}})},function(A,M,t){"use strict";t(27)("big",function(A){return function(){return A(this,"big","","")}})},function(A,M,t){"use strict";t(27)("blink",function(A){return function(){return A(this,"blink","","")}})},function(A,M,t){"use strict";t(27)("bold",function(A){return function(){return A(this,"b","","")}})},function(A,M,t){"use strict";var I=t(1),g=t(154)(!1);I(I.P,"String",{codePointAt:function(A){return g(this,A)}})},function(A,M,t){"use strict";var I=t(1),g=t(17),e=t(155),i="endsWith",T=""[i];I(I.P+I.F*t(141)(i),"String",{endsWith:function(A){var M=e(this,A,i),t=arguments.length>1?arguments[1]:void 0,I=g(M.length),E=void 0===t?I:Math.min(g(t),I),N=String(A);return T?T.call(M,N,E):M.slice(E-N.length,E)===N}})},function(A,M,t){"use strict";t(27)("fixed",function(A){return function(){return A(this,"tt","","")}})},function(A,M,t){"use strict";t(27)("fontcolor",function(A){return function(M){return A(this,"font","color",M)}})},function(A,M,t){"use strict";t(27)("fontsize",function(A){return function(M){return A(this,"font","size",M)}})},function(A,M,t){var I=t(1),g=t(70),e=String.fromCharCode,i=String.fromCodePoint;I(I.S+I.F*(!!i&&1!=i.length),"String",{fromCodePoint:function(A){for(var M,t=[],I=arguments.length,i=0;I>i;){if(M=+arguments[i++],g(M,1114111)!==M)throw RangeError(M+" is not a valid code point");t.push(M<65536?e(M):e(((M-=65536)>>10)+55296,M%1024+56320))}return t.join("")}})},function(A,M,t){"use strict";var I=t(1),g=t(155),e="includes";I(I.P+I.F*t(141)(e),"String",{includes:function(A){return!!~g(this,A,e).indexOf(A,arguments.length>1?arguments[1]:void 0)}})},function(A,M,t){"use strict";t(27)("italics",function(A){return function(){return A(this,"i","","")}})},function(A,M,t){"use strict";var I=t(154)(!0);t(147)(String,"String",function(A){this._t=String(A),this._i=0},function(){var A,M=this._t,t=this._i;return t>=M.length?{value:void 0,done:!0}:(A=I(M,t),this._i+=A.length,{value:A,done:!1})})},function(A,M,t){"use strict";t(27)("link",function(A){return function(M){return A(this,"a","href",M)}})},function(A,M,t){var I=t(1),g=t(28),e=t(17);I(I.S,"String",{raw:function(A){for(var M=g(A.raw),t=e(M.length),I=arguments.length,i=[],T=0;t>T;)i.push(String(M[T++])),T1?arguments[1]:void 0,M.length)),I=String(A);return T?T.call(M,I,t):M.slice(t,t+I.length)===I}})},function(A,M,t){"use strict";t(27)("strike",function(A){return function(){return A(this,"strike","","")}})},function(A,M,t){"use strict";t(27)("sub",function(A){return function(){return A(this,"sub","","")}})},function(A,M,t){"use strict";t(27)("sup",function(A){return function(){return A(this,"sup","","")}})},function(A,M,t){"use strict";t(82)("trim",function(A){return function(){return A(this,3)}})},function(A,M,t){"use strict";var I=t(5),g=t(21),e=t(13),i=t(1),T=t(26),E=t(55).KEY,N=t(6),n=t(114),o=t(81),c=t(71),C=t(11),a=t(240),D=t(160),r=t(376),B=t(375),Q=t(145),s=t(4),u=t(28),x=t(43),y=t(56),j=t(65),l=t(232),w=t(30),L=t(14),Y=t(67),d=w.f,h=L.f,S=l.f,z=I.Symbol,p=I.JSON,U=p&&p.stringify,O="prototype",f=C("_hidden"),m=C("toPrimitive"),F={}.propertyIsEnumerable,k=n("symbol-registry"),R=n("symbols"),J=n("op-symbols"),G=Object[O],H="function"==typeof z,v=I.QObject,b=!v||!v[O]||!v[O].findChild,X=e&&N(function(){return 7!=j(h({},"a",{get:function(){return h(this,"a",{value:7}).a}})).a})?function(A,M,t){var I=d(G,M);I&&delete G[M],h(A,M,t),I&&A!==G&&h(G,M,I)}:h,W=function(A){var M=R[A]=j(z[O]);return M._k=A,M},V=H&&"symbol"==typeof z.iterator?function(A){return"symbol"==typeof A}:function(A){return A instanceof z},P=function(A,M,t){return A===G&&P(J,M,t),s(A),M=x(M,!0),s(t),g(R,M)?(t.enumerable?(g(A,f)&&A[f][M]&&(A[f][M]=!1),t=j(t,{enumerable:y(0,!1)})):(g(A,f)||h(A,f,y(1,{})),A[f][M]=!0),X(A,M,t)):h(A,M,t)},Z=function(A,M){s(A);for(var t,I=B(M=u(M)),g=0,e=I.length;e>g;)P(A,t=I[g++],M[t]);return A},K=function(A,M){return void 0===M?j(A):Z(j(A),M)},q=function(A){var M=F.call(this,A=x(A,!0));return!(this===G&&g(R,A)&&!g(J,A))&&(!(M||!g(this,A)||!g(R,A)||g(this,f)&&this[f][A])||M)},_=function(A,M){if(A=u(A),M=x(M,!0),A!==G||!g(R,M)||g(J,M)){var t=d(A,M);return!t||!g(R,M)||g(A,f)&&A[f][M]||(t.enumerable=!0),t}},$=function(A){for(var M,t=S(u(A)),I=[],e=0;t.length>e;)g(R,M=t[e++])||M==f||M==E||I.push(M);return I},AA=function(A){for(var M,t=A===G,I=S(t?J:u(A)),e=[],i=0;I.length>i;)!g(R,M=I[i++])||t&&!g(G,M)||e.push(R[M]);return e};H||(z=function(){if(this instanceof z)throw TypeError("Symbol is not a constructor!");var A=c(arguments.length>0?arguments[0]:void 0),M=function(t){this===G&&M.call(J,t), g(this,f)&&g(this[f],A)&&(this[f][A]=!1),X(this,A,y(1,t))};return e&&b&&X(G,A,{configurable:!0,set:M}),W(A)},T(z[O],"toString",function(){return this._k}),w.f=_,L.f=P,t(66).f=l.f=$,t(96).f=q,t(113).f=AA,e&&!t(64)&&T(G,"propertyIsEnumerable",q,!0),a.f=function(A){return W(C(A))}),i(i.G+i.W+i.F*!H,{Symbol:z});for(var MA="hasInstance,isConcatSpreadable,iterator,match,replace,search,species,split,toPrimitive,toStringTag,unscopables".split(","),tA=0;MA.length>tA;)C(MA[tA++]);for(var MA=Y(C.store),tA=0;MA.length>tA;)D(MA[tA++]);i(i.S+i.F*!H,"Symbol",{for:function(A){return g(k,A+="")?k[A]:k[A]=z(A)},keyFor:function(A){if(V(A))return r(k,A);throw TypeError(A+" is not a symbol!")},useSetter:function(){b=!0},useSimple:function(){b=!1}}),i(i.S+i.F*!H,"Object",{create:K,defineProperty:P,defineProperties:Z,getOwnPropertyDescriptor:_,getOwnPropertyNames:$,getOwnPropertySymbols:AA}),p&&i(i.S+i.F*(!H||N(function(){var A=z();return"[null]"!=U([A])||"{}"!=U({a:A})||"{}"!=U(Object(A))})),"JSON",{stringify:function(A){if(void 0!==A&&!V(A)){for(var M,t,I=[A],g=1;arguments.length>g;)I.push(arguments[g++]);return M=I[1],"function"==typeof M&&(t=M),!t&&Q(M)||(M=function(A,M){if(t&&(M=t.call(this,A,M)),!V(M))return M}),I[1]=M,U.apply(p,I)}}}),z[O][m]||t(25)(z[O],m,z[O].valueOf),o(z,"Symbol"),o(Math,"Math",!0),o(I.JSON,"JSON",!0)},function(A,M,t){"use strict";var I=t(1),g=t(115),e=t(159),i=t(4),T=t(70),E=t(17),N=t(9),n=t(5).ArrayBuffer,o=t(153),c=e.ArrayBuffer,C=e.DataView,a=g.ABV&&n.isView,D=c.prototype.slice,r=g.VIEW,B="ArrayBuffer";I(I.G+I.W+I.F*(n!==c),{ArrayBuffer:c}),I(I.S+I.F*!g.CONSTR,B,{isView:function(A){return a&&a(A)||N(A)&&r in A}}),I(I.P+I.U+I.F*t(6)(function(){return!new c(2).slice(1,void 0).byteLength}),B,{slice:function(A,M){if(void 0!==D&&void 0===M)return D.call(i(this),A);for(var t=i(this).byteLength,I=T(A,t),g=T(void 0===M?t:M,t),e=new(o(this,c))(E(g-I)),N=new C(this),n=new C(e),a=0;I0?arguments[0]:void 0)}},{add:function(A){return I.def(this,A,!0)}},I,!1,!0)},function(A,M,t){"use strict";var I=t(1),g=t(105)(!0);I(I.P,"Array",{includes:function(A){return g(this,A,arguments.length>1?arguments[1]:void 0)}}),t(78)("includes")},function(A,M,t){var I=t(1),g=t(150)(),e=t(5).process,i="process"==t(35)(e);I(I.G,{asap:function(A){var M=i&&e.domain;g(M?M.bind(A):A)}})},function(A,M,t){var I=t(1),g=t(35);I(I.S,"Error",{isError:function(A){return"Error"===g(A)}})},function(A,M,t){var I=t(1);I(I.P+I.R,"Map",{toJSON:t(223)("Map")})},function(A,M,t){var I=t(1);I(I.S,"Math",{iaddh:function(A,M,t,I){var g=A>>>0,e=M>>>0,i=t>>>0;return e+(I>>>0)+((g&i|(g|i)&~(g+i>>>0))>>>31)|0}})},function(A,M,t){var I=t(1);I(I.S,"Math",{imulh:function(A,M){var t=65535,I=+A,g=+M,e=I&t,i=g&t,T=I>>16,E=g>>16,N=(T*i>>>0)+(e*i>>>16);return T*E+(N>>16)+((e*E>>>0)+(N&t)>>16)}})},function(A,M,t){var I=t(1);I(I.S,"Math",{isubh:function(A,M,t,I){var g=A>>>0,e=M>>>0,i=t>>>0;return e-(I>>>0)-((~g&i|~(g^i)&g-i>>>0)>>>31)|0}})},function(A,M,t){var I=t(1);I(I.S,"Math",{umulh:function(A,M){var t=65535,I=+A,g=+M,e=I&t,i=g&t,T=I>>>16,E=g>>>16,N=(T*i>>>0)+(e*i>>>16);return T*E+(N>>>16)+((e*E>>>0)+(N&t)>>>16)}})},function(A,M,t){"use strict";var I=t(1),g=t(19),e=t(24),i=t(14);t(13)&&I(I.P+t(112),"Object",{__defineGetter__:function(A,M){i.f(g(this),A,{get:e(M),enumerable:!0,configurable:!0})}})},function(A,M,t){"use strict";var I=t(1),g=t(19),e=t(24),i=t(14);t(13)&&I(I.P+t(112),"Object",{__defineSetter__:function(A,M){i.f(g(this),A,{set:e(M),enumerable:!0,configurable:!0})}})},function(A,M,t){var I=t(1),g=t(234)(!0);I(I.S,"Object",{entries:function(A){return g(A)}})},function(A,M,t){var I=t(1),g=t(235),e=t(28),i=t(30),T=t(138);I(I.S,"Object",{getOwnPropertyDescriptors:function(A){for(var M,t=e(A),I=i.f,E=g(t),N={},n=0;E.length>n;)T(N,M=E[n++],I(t,M));return N}})},function(A,M,t){"use strict";var I=t(1),g=t(19),e=t(43),i=t(31),T=t(30).f;t(13)&&I(I.P+t(112),"Object",{__lookupGetter__:function(A){var M,t=g(this),I=e(A,!0);do if(M=T(t,I))return M.get;while(t=i(t))}})},function(A,M,t){"use strict";var I=t(1),g=t(19),e=t(43),i=t(31),T=t(30).f;t(13)&&I(I.P+t(112),"Object",{__lookupSetter__:function(A){var M,t=g(this),I=e(A,!0);do if(M=T(t,I))return M.set;while(t=i(t))}})},function(A,M,t){var I=t(1),g=t(234)(!1);I(I.S,"Object",{values:function(A){return g(A)}})},function(A,M,t){"use strict";var I=t(1),g=t(5),e=t(48),i=t(150)(),T=t(11)("observable"),E=t(24),N=t(4),n=t(63),o=t(68),c=t(25),C=t(79),a=C.RETURN,D=function(A){return null==A?void 0:E(A)},r=function(A){var M=A._c;M&&(A._c=void 0,M())},B=function(A){return void 0===A._o},Q=function(A){B(A)||(A._o=void 0,r(A))},s=function(A,M){N(A),this._c=void 0,this._o=A,A=new u(this);try{var t=M(A),I=t;null!=t&&("function"==typeof t.unsubscribe?t=function(){I.unsubscribe()}:E(t),this._c=t)}catch(M){return void A.error(M)}B(this)&&r(this)};s.prototype=o({},{unsubscribe:function(){Q(this)}});var u=function(A){this._s=A};u.prototype=o({},{next:function(A){var M=this._s;if(!B(M)){var t=M._o;try{var I=D(t.next);if(I)return I.call(t,A)}catch(A){try{Q(M)}finally{throw A}}}},error:function(A){var M=this._s;if(B(M))throw A;var t=M._o;M._o=void 0;try{var I=D(t.error);if(!I)throw A;A=I.call(t,A)}catch(A){try{r(M)}finally{throw A}}return r(M),A},complete:function(A){var M=this._s;if(!B(M)){var t=M._o;M._o=void 0;try{var I=D(t.complete);A=I?I.call(t,A):void 0}catch(A){try{r(M)}finally{throw A}}return r(M),A}}});var x=function(A){n(this,x,"Observable","_f")._f=E(A)};o(x.prototype,{subscribe:function(A){return new s(A,this._f)},forEach:function(A){var M=this;return new(e.Promise||g.Promise)(function(t,I){E(A);var g=M.subscribe({next:function(M){try{return A(M)}catch(A){I(A),g.unsubscribe()}},error:I,complete:t})})}}),o(x,{from:function(A){var M="function"==typeof this?this:x,t=D(N(A)[T]);if(t){var I=N(t.call(A));return I.constructor===M?I:new M(function(A){return I.subscribe(A)})}return new M(function(M){var t=!1;return i(function(){if(!t){try{if(C(A,!1,function(A){if(M.next(A),t)return a})===a)return}catch(A){if(t)throw A;return void M.error(A)}M.complete()}}),function(){t=!0}})},of:function(){for(var A=0,M=arguments.length,t=Array(M);A1?arguments[1]:void 0,!1)}})},function(A,M,t){"use strict";var I=t(1),g=t(239);I(I.P,"String",{padStart:function(A){return g(this,A,arguments.length>1?arguments[1]:void 0,!0)}})},function(A,M,t){"use strict";t(82)("trimLeft",function(A){return function(){return A(this,1)}},"trimStart")},function(A,M,t){"use strict";t(82)("trimRight",function(A){return function(){return A(this,2)}},"trimEnd")},function(A,M,t){t(160)("asyncIterator")},function(A,M,t){t(160)("observable")},function(A,M,t){var I=t(1);I(I.S,"System",{global:t(5)})},function(A,M,t){for(var I=t(162),g=t(26),e=t(5),i=t(25),T=t(80),E=t(11),N=E("iterator"),n=E("toStringTag"),o=T.Array,c=["NodeList","DOMTokenList","MediaList","StyleSheetList","CSSRuleList"],C=0;C<5;C++){var a,D=c[C],r=e[D],B=r&&r.prototype;if(B){B[N]||i(B,N,o),B[n]||i(B,n,D),T[D]=o;for(a in I)B[a]||g(B,a,I[a],!0)}}},function(A,M,t){var I=t(1),g=t(158);I(I.G+I.B,{setImmediate:g.set,clearImmediate:g.clear})},function(A,M,t){var I=t(5),g=t(1),e=t(109),i=t(377),T=I.navigator,E=!!T&&/MSIE .\./.test(T.userAgent),N=function(A){return E?function(M,t){return A(e(i,[].slice.call(arguments,2),"function"==typeof M?M:Function(M)),t)}:A};g(g.G+g.B+g.F*E,{setTimeout:N(I.setTimeout),setInterval:N(I.setInterval)})},function(A,M,t){t(500),t(439),t(441),t(440),t(443),t(445),t(450),t(444),t(442),t(452),t(451),t(447),t(448),t(446),t(438),t(449),t(453),t(454),t(406),t(408),t(407),t(456),t(455),t(426),t(436),t(437),t(427),t(428),t(429),t(430),t(431),t(432),t(433),t(434),t(435),t(409),t(410),t(411),t(412),t(413),t(414),t(415),t(416),t(417),t(418),t(419),t(420),t(421),t(422),t(423),t(424),t(425),t(487),t(492),t(499),t(490),t(482),t(483),t(488),t(493),t(495),t(478),t(479),t(480),t(481),t(484),t(485),t(486),t(489),t(491),t(494),t(496),t(497),t(498),t(401),t(403),t(402),t(405),t(404),t(390),t(388),t(394),t(391),t(397),t(399),t(387),t(393),t(384),t(398),t(382),t(396),t(395),t(389),t(392),t(381),t(383),t(386),t(385),t(400),t(162),t(472),t(477),t(242),t(473),t(474),t(475),t(476),t(457),t(241),t(243),t(244),t(512),t(501),t(502),t(507),t(510),t(511),t(505),t(508),t(506),t(509),t(503),t(504),t(458),t(459),t(460),t(461),t(462),t(465),t(463),t(464),t(466),t(467),t(468),t(469),t(471),t(470),t(513),t(539),t(542),t(541),t(543),t(544),t(540),t(545),t(546),t(524),t(527),t(523),t(521),t(522),t(525),t(526),t(516),t(538),t(547),t(515),t(517),t(519),t(518),t(520),t(529),t(530),t(532),t(531),t(534),t(533),t(535),t(536),t(537),t(514),t(528),t(550),t(549),t(548),A.exports=t(48)},function(A,M,t){M=A.exports=t(245)(),M.push([A.id,"/*!\n * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome\n * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)\n */@font-face{font-family:FontAwesome;src:url("+t(805)+");src:url("+t(804)+'?#iefix&v=4.7.0) format("embedded-opentype"),url('+t(808)+') format("woff2"),url('+t(809)+') format("woff"),url('+t(807)+') format("truetype"),url('+t(806)+'#fontawesomeregular) format("svg");font-weight:400;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{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(245)(),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(800)+') format("woff2"),url('+t(799)+') 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(803)+') 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!important;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!important;border-color:hsla(0,0%,100%,.1)!important}.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(801)+') 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(347)+') 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(347)+') 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(802)+') 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){M=A.exports=t(245)(),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(800)+') format("woff2"),url('+t(799)+') 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(803)+') 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!important;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!important;border-color:hsla(0,0%,100%,.1)!important}.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;position:relative}.set-expire:before{content:"";position:absolute;left:0;top:0;width:100%;height:100%;z-index:1}.set-expire-item{padding:9px 5px 3px;position:relative;display:table-cell;width:1%;text-align:center;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.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{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;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(801)+') 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(347)+') 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(347)+') 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(802)+') 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,n;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(o[e]!=c[e])return!1;for(e=o.length-1;e>=0;e--)if(n=o[e],!N(A[n],M[n],t))return!1;return typeof A==typeof M}var i=Array.prototype.slice,T=t(556),E=t(555),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(248);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(557),removeClass:t(559),hasClass:t(248)}},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(84),g=t(564);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(163),g=t(249),e=t(560);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(117);M.__esModule=!0,M.default=g;var i=t(83),T=e.interopRequireDefault(i),E=t(164),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,D.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,D.default)(M,"borderTopWidth"),10)-(0,o.default)(M)||0,g.left+=parseInt((0,D.default)(M,"borderLeftWidth"),10)-(0,C.default)(M)||0),e._extends({},t,{top:t.top-g.top-(parseInt((0,D.default)(A,"marginTop"),10)||0),left:t.left-g.left-(parseInt((0,D.default)(A,"marginLeft"),10)||0)})}var e=t(117);M.__esModule=!0,M.default=g;var i=t(250),T=e.interopRequireDefault(i),E=t(562),N=e.interopRequireDefault(E),n=t(251),o=e.interopRequireDefault(n),c=t(565),C=e.interopRequireDefault(c),a=t(164),D=e.interopRequireDefault(a);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(116);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(117),g=t(252),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(72),N=Object.prototype.hasOwnProperty,n="transform",o={};E&&(o=I(),n=o.prefix+n,i=o.prefix+"transition-property",e=o.prefix+"transition-duration",T=o.prefix+"transition-delay",g=o.prefix+"transition-timing-function"),A.exports={transform:n,end:o.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(570),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(572),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(583);A.exports=g},function(A,M,t){"use strict";function I(A){var M=A.match(n);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 n=e[0];n--;)t=t.lastChild}else t.innerHTML=A;var o=t.getElementsByTagName("script");o.length&&(M?void 0:E(!1),i(o).forEach(M));for(var c=i(t.childNodes);t.lastChild;)t.removeChild(t.lastChild);return c}var e=t(20),i=t(574),T=t(258),E=t(3),N=e.canUseDOM?document.createElement("div"):null,n=/^\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(577),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(579);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;I1&&(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+=f(A>>>10&1023|55296),A=56320|1023&A),M+=f(A)}).join("")}function o(A){return A-48<10?A-22:A-65<26?A-65:A-97<26?A-97:x}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/w):A>>1,A+=O(A/M);A>U*j>>1;I+=x)A=O(A/U);return O(I+(U+1)*A/(A+l))}function a(A){var M,t,I,g,e,T,E,N,c,a,D=[],r=A.length,B=0,Q=Y,s=L;for(t=A.lastIndexOf(d),t<0&&(t=0),I=0;I=128&&i("not-basic"),D.push(A.charCodeAt(I));for(g=t>0?t+1:0;g=r&&i("invalid-input"),N=o(A.charCodeAt(g++)),(N>=x||N>O((u-B)/T))&&i("overflow"),B+=N*T,c=E<=s?y:E>=s+j?j:E-s,!(NO(u/a)&&i("overflow"),T*=a;M=D.length+1,s=C(B-e,M,0==e),O(B/M)>u-Q&&i("overflow"),Q+=O(B/M),B%=M,D.splice(B++,0,Q)}return n(D)}function D(A){var M,t,I,g,e,T,E,n,o,a,D,r,B,Q,s,l=[];for(A=N(A),r=A.length,M=Y,t=0,e=L,T=0;T=M&&DO((u-t)/B)&&i("overflow"),t+=(E-M)*B,M=E,T=0;Tu&&i("overflow"),D==M){for(n=t,o=x;a=o<=e?y:o>=e+j?j:o-e,!(n= 0x80 (not a basic code point)","invalid-input":"Invalid input"},U=x-y,O=Math.floor,f=String.fromCharCode;s={version:"1.3.2",ucs2:{decode:N,encode:n},decode:a,encode:D,toASCII:B,toUnicode:r},I=function(){return s}.call(M,t,M,A),!(void 0!==I&&(A.exports=I))}(this)}).call(M,t(215)(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(810),N=t(812);M.parse=g,M.resolve=i,M.resolveObject=T,M.format=e,M.Url=I;var n=/^([a-z0-9.+-]+:)/i,o=/:[0-9]*$/,c=/^(\/\/?(?!\/)[^\?\s]*)(\?[^\s]*)?$/,C=["<",">",'"',"` + "`" + `"," ","\r","\n","\t"],a=["{","}","|","\\","^","` + "`" + `"].concat(C),D=["'"].concat(a),r=["%","/","?",";","#"].concat(D),B=["/","?","#"],Q=255,s=/^[+a-z0-9A-Z_-]{0,63}$/,u=/^([+a-z0-9A-Z_-]{0,63})(.*)$/,x={javascript:!0,"javascript:":!0},y={javascript:!0,"javascript:":!0},j={http:!0,https:!0,ftp:!0,gopher:!0,file:!0,"http:":!0,"https:":!0,"ftp:":!0,"gopher:":!0,"file:":!0},l=t(644);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[m];if(!f.match(s)){var k=p.slice(0,Y),R=p.slice(Y+1),J=O.match(u);J&&(k.push(J[1]),R.unshift(J[2])),R.length&&(T="/"+R.join(".")+T),this.hostname=k.join(".");break}}}this.hostname.length>Q?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(!x[a])for(var Y=0,U=D.length;Y0)&&t.host.split("@");w&&(t.auth=w.shift(),t.host=t.hostname=w.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(!x.length)return t.pathname=null,t.search?t.path="/"+t.search:t.path=null,t.href=t.format(),t;for(var L=x.slice(-1)[0],Y=(t.host||A.host||x.length>1)&&("."===L||".."===L)||""===L,d=0,h=x.length;h>=0;h--)L=x[h],"."===L?x.splice(h,1):".."===L?(x.splice(h,1),d++):d&&(x.splice(h,1),d--);if(!s&&!u)for(;d--;d)x.unshift("..");!s||""===x[0]||x[0]&&"/"===x[0].charAt(0)||x.unshift(""),Y&&"/"!==x.join("/").substr(-1)&&x.push("");var S=""===x[0]||x[0]&&"/"===x[0].charAt(0);if(l){t.hostname=t.host=S?"":x.length?x.shift():"";var w=!!(t.host&&t.host.indexOf("@")>0)&&t.host.split("@");w&&(t.auth=w.shift(),t.host=t.hostname=w.shift())}return s=s||t.host&&x.length,s&&!S&&x.unshift(""),x.length?t.pathname=x.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=o.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_bundle20170504t205852zJsBytes() ([]byte, error) { - return _productionIndex_bundle20170504t205852zJs, nil +func productionIndex_bundle20170602t213618zJsBytes() ([]byte, error) { + return _productionIndex_bundle20170602t213618zJs, nil } -func productionIndex_bundle20170504t205852zJs() (*asset, error) { - bytes, err := productionIndex_bundle20170504t205852zJsBytes() +func productionIndex_bundle20170602t213618zJs() (*asset, error) { + bytes, err := productionIndex_bundle20170602t213618zJsBytes() if err != nil { return nil, err } - info := bindataFileInfo{name: "production/index_bundle-2017-05-04T20-58-52Z.js", size: 2367669, mode: os.FileMode(436), modTime: time.Unix(1493931552, 0)} + info := bindataFileInfo{name: "production/index_bundle-2017-06-02T21-36-18Z.js", size: 2368338, mode: os.FileMode(436), modTime: time.Unix(1496439395, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -371,7 +371,7 @@ func productionLoaderCss() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "production/loader.css", size: 1738, mode: os.FileMode(436), modTime: time.Unix(1493931552, 0)} + info := bindataFileInfo{name: "production/loader.css", size: 1738, mode: os.FileMode(436), modTime: time.Unix(1496439395, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -445,7 +445,7 @@ func productionLogoSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "production/logo.svg", size: 3079, mode: os.FileMode(436), modTime: time.Unix(1493931552, 0)} + info := bindataFileInfo{name: "production/logo.svg", size: 3079, mode: os.FileMode(436), modTime: time.Unix(1496439395, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -462,7 +462,7 @@ func productionSafariPng() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "production/safari.png", size: 4971, mode: os.FileMode(436), modTime: time.Unix(1493931552, 0)} + info := bindataFileInfo{name: "production/safari.png", size: 4971, mode: os.FileMode(436), modTime: time.Unix(1496439395, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -523,7 +523,7 @@ var _bindata = map[string]func() (*asset, error){ "production/favicon.ico": productionFaviconIco, "production/firefox.png": productionFirefoxPng, "production/index.html": productionIndexHTML, - "production/index_bundle-2017-05-04T20-58-52Z.js": productionIndex_bundle20170504t205852zJs, + "production/index_bundle-2017-06-02T21-36-18Z.js": productionIndex_bundle20170602t213618zJs, "production/loader.css": productionLoaderCss, "production/logo.svg": productionLogoSvg, "production/safari.png": productionSafariPng, @@ -575,7 +575,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-05-04T20-58-52Z.js": {productionIndex_bundle20170504t205852zJs, map[string]*bintree{}}, + "index_bundle-2017-06-02T21-36-18Z.js": {productionIndex_bundle20170602t213618zJs, map[string]*bintree{}}, "loader.css": {productionLoaderCss, map[string]*bintree{}}, "logo.svg": {productionLogoSvg, map[string]*bintree{}}, "safari.png": {productionSafariPng, map[string]*bintree{}}, @@ -639,6 +639,6 @@ func assetFS() *assetfs.AssetFS { panic("unreachable") } -var UIReleaseTag = "RELEASE.2017-05-04T20-58-52Z" -var UICommitID = "96e61fe879764dce2c947fbebfff5e80092df21b" -var UIVersion = "2017-05-04T20:58:52Z" +var UIReleaseTag = "RELEASE.2017-06-02T21-36-18Z" +var UICommitID = "432bf7d99e5c24c074a1c2bac0f2fadc9891e0a0" +var UIVersion = "2017-06-02T21:36:18Z" From 2559614bfde1d338dc206d0dd56df68c6eeab235 Mon Sep 17 00:00:00 2001 From: poornas Date: Mon, 5 Jun 2017 08:11:54 -0700 Subject: [PATCH 56/80] fix: Set UIversion in reply for policy API (#4469) --- cmd/web-handlers.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/web-handlers.go b/cmd/web-handlers.go index bc4992e2e..99f74333f 100644 --- a/cmd/web-handlers.go +++ b/cmd/web-handlers.go @@ -773,6 +773,8 @@ type SetBucketPolicyArgs struct { // SetBucketPolicy - set bucket policy. func (web *webAPIHandlers) SetBucketPolicy(r *http.Request, args *SetBucketPolicyArgs, reply *WebGenericRep) error { objectAPI := web.ObjectAPI() + reply.UIVersion = browser.UIVersion + if objectAPI == nil { return toJSONError(errServerNotInitialized) } @@ -804,7 +806,6 @@ func (web *webAPIHandlers) SetBucketPolicy(r *http.Request, args *SetBucketPolic if err != nil { return toJSONError(err) } - reply.UIVersion = browser.UIVersion return nil } @@ -813,7 +814,6 @@ func (web *webAPIHandlers) SetBucketPolicy(r *http.Request, args *SetBucketPolic if err != nil { return toJSONError(err, args.BucketName) } - reply.UIVersion = browser.UIVersion return nil } data, err := json.Marshal(policyInfo) From 986aa8fabf30dcf85994710417db71d1d7503cbc Mon Sep 17 00:00:00 2001 From: Aditya Manthramurthy Date: Mon, 5 Jun 2017 12:25:04 -0700 Subject: [PATCH 57/80] Bypass network in lock requests to local server (#4465) This makes lock RPCs similar to other RPCs where requests to the local server bypass the network. Requests to the local lock-subsystem may bypass the network layer and directly access the locking data-structures. This incidentally fixes #4451. --- cmd/lock-rpc-server-common.go | 4 +- cmd/lock-rpc-server-common_test.go | 24 +-- cmd/lock-rpc-server.go | 232 ++++++++++++++++------------- cmd/lock-rpc-server_test.go | 36 +++-- cmd/namespace-lock.go | 53 +++++-- cmd/server-main.go | 4 +- cmd/server-mux_test.go | 3 +- 7 files changed, 210 insertions(+), 146 deletions(-) diff --git a/cmd/lock-rpc-server-common.go b/cmd/lock-rpc-server-common.go index ef65ca56e..124b46496 100644 --- a/cmd/lock-rpc-server-common.go +++ b/cmd/lock-rpc-server-common.go @@ -22,7 +22,7 @@ import ( ) // Similar to removeEntry but only removes an entry only if the lock entry exists in map. -func (l *lockServer) removeEntryIfExists(nlrip nameLockRequesterInfoPair) { +func (l *localLocker) removeEntryIfExists(nlrip nameLockRequesterInfoPair) { // Check if entry is still in map (could have been removed altogether by 'concurrent' (R)Unlock of last entry) if lri, ok := l.lockMap[nlrip.name]; ok { if !l.removeEntry(nlrip.name, nlrip.lri.uid, &lri) { @@ -38,7 +38,7 @@ func (l *lockServer) removeEntryIfExists(nlrip nameLockRequesterInfoPair) { // removeEntry either, based on the uid of the lock message, removes a single entry from the // lockRequesterInfo array or the whole array from the map (in case of a write lock or last read lock) -func (l *lockServer) removeEntry(name, uid string, lri *[]lockRequesterInfo) bool { +func (l *localLocker) removeEntry(name, uid string, lri *[]lockRequesterInfo) bool { // Find correct entry to remove based on uid. for index, entry := range *lri { if entry.uid == uid { diff --git a/cmd/lock-rpc-server-common_test.go b/cmd/lock-rpc-server-common_test.go index 24dc244c7..abb7f6405 100644 --- a/cmd/lock-rpc-server-common_test.go +++ b/cmd/lock-rpc-server-common_test.go @@ -38,9 +38,9 @@ func TestLockRpcServerRemoveEntryIfExists(t *testing.T) { nlrip := nameLockRequesterInfoPair{name: "name", lri: lri} // first test by simulating item has already been deleted - locker.removeEntryIfExists(nlrip) + locker.ll.removeEntryIfExists(nlrip) { - gotLri, _ := locker.lockMap["name"] + gotLri, _ := locker.ll.lockMap["name"] expectedLri := []lockRequesterInfo(nil) if !reflect.DeepEqual(expectedLri, gotLri) { t.Errorf("Expected %#v, got %#v", expectedLri, gotLri) @@ -48,10 +48,10 @@ func TestLockRpcServerRemoveEntryIfExists(t *testing.T) { } // then test normal deletion - locker.lockMap["name"] = []lockRequesterInfo{lri} // add item - locker.removeEntryIfExists(nlrip) + locker.ll.lockMap["name"] = []lockRequesterInfo{lri} // add item + locker.ll.removeEntryIfExists(nlrip) { - gotLri, _ := locker.lockMap["name"] + gotLri, _ := locker.ll.lockMap["name"] expectedLri := []lockRequesterInfo(nil) if !reflect.DeepEqual(expectedLri, gotLri) { t.Errorf("Expected %#v, got %#v", expectedLri, gotLri) @@ -81,32 +81,32 @@ func TestLockRpcServerRemoveEntry(t *testing.T) { timeLastCheck: UTCNow(), } - locker.lockMap["name"] = []lockRequesterInfo{ + locker.ll.lockMap["name"] = []lockRequesterInfo{ lockRequesterInfo1, lockRequesterInfo2, } - lri, _ := locker.lockMap["name"] + lri, _ := locker.ll.lockMap["name"] // test unknown uid - if locker.removeEntry("name", "unknown-uid", &lri) { + if locker.ll.removeEntry("name", "unknown-uid", &lri) { t.Errorf("Expected %#v, got %#v", false, true) } - if !locker.removeEntry("name", "0123-4567", &lri) { + if !locker.ll.removeEntry("name", "0123-4567", &lri) { t.Errorf("Expected %#v, got %#v", true, false) } else { - gotLri, _ := locker.lockMap["name"] + gotLri, _ := locker.ll.lockMap["name"] expectedLri := []lockRequesterInfo{lockRequesterInfo2} if !reflect.DeepEqual(expectedLri, gotLri) { t.Errorf("Expected %#v, got %#v", expectedLri, gotLri) } } - if !locker.removeEntry("name", "89ab-cdef", &lri) { + if !locker.ll.removeEntry("name", "89ab-cdef", &lri) { t.Errorf("Expected %#v, got %#v", true, false) } else { - gotLri, _ := locker.lockMap["name"] + gotLri, _ := locker.ll.lockMap["name"] expectedLri := []lockRequesterInfo(nil) if !reflect.DeepEqual(expectedLri, gotLri) { t.Errorf("Expected %#v, got %#v", expectedLri, gotLri) diff --git a/cmd/lock-rpc-server.go b/cmd/lock-rpc-server.go index e193f42cd..6de6a965d 100644 --- a/cmd/lock-rpc-server.go +++ b/cmd/lock-rpc-server.go @@ -60,9 +60,7 @@ func isWriteLock(lri []lockRequesterInfo) bool { // lockServer is type for RPC handlers type lockServer struct { AuthRPCServer - serviceEndpoint string - mutex sync.Mutex - lockMap map[string][]lockRequesterInfo + ll localLocker } // Start lock maintenance from all lock servers. @@ -91,30 +89,11 @@ func startLockMaintainence(lockServers []*lockServer) { // Register distributed NS lock handlers. func registerDistNSLockRouter(mux *router.Router, endpoints EndpointList) error { - // Initialize a new set of lock servers. - lockServers := newLockServers(endpoints) - // Start lock maintenance from all lock servers. - startLockMaintainence(lockServers) + startLockMaintainence(globalLockServers) // Register initialized lock servers to their respective rpc endpoints. - return registerStorageLockers(mux, lockServers) -} - -// Create one lock server for every local storage rpc server. -func newLockServers(endpoints EndpointList) (lockServers []*lockServer) { - for _, endpoint := range endpoints { - // Initialize new lock server for each local node. - if endpoint.IsLocal { - lockServers = append(lockServers, &lockServer{ - serviceEndpoint: endpoint.Path, - mutex: sync.Mutex{}, - lockMap: make(map[string][]lockRequesterInfo), - }) - } - } - - return lockServers + return registerStorageLockers(mux, globalLockServers) } // registerStorageLockers - register locker rpc handlers for net/rpc library clients @@ -125,129 +104,178 @@ func registerStorageLockers(mux *router.Router, lockServers []*lockServer) error return traceError(err) } lockRouter := mux.PathPrefix(minioReservedBucketPath).Subrouter() - lockRouter.Path(path.Join(lockServicePath, lockServer.serviceEndpoint)).Handler(lockRPCServer) + lockRouter.Path(path.Join(lockServicePath, lockServer.ll.serviceEndpoint)).Handler(lockRPCServer) } return nil } -/// Distributed lock handlers +// localLocker implements Dsync.NetLocker +type localLocker struct { + mutex sync.Mutex + serviceEndpoint string + serverAddr string + lockMap map[string][]lockRequesterInfo +} -// Lock - rpc handler for (single) write lock operation. -func (l *lockServer) Lock(args *LockArgs, reply *bool) error { +func (l *localLocker) ServerAddr() string { + return l.serverAddr +} + +func (l *localLocker) ServiceEndpoint() string { + return l.serviceEndpoint +} + +func (l *localLocker) Lock(args dsync.LockArgs) (reply bool, err error) { l.mutex.Lock() defer l.mutex.Unlock() - if err := args.IsAuthenticated(); err != nil { - return err - } - _, *reply = l.lockMap[args.LockArgs.Resource] - if !*reply { // No locks held on the given name, so claim write lock - l.lockMap[args.LockArgs.Resource] = []lockRequesterInfo{ + _, isLockTaken := l.lockMap[args.Resource] + if !isLockTaken { // No locks held on the given name, so claim write lock + l.lockMap[args.Resource] = []lockRequesterInfo{ { writer: true, - node: args.LockArgs.ServerAddr, - serviceEndpoint: args.LockArgs.ServiceEndpoint, - uid: args.LockArgs.UID, + node: args.ServerAddr, + serviceEndpoint: args.ServiceEndpoint, + uid: args.UID, timestamp: UTCNow(), timeLastCheck: UTCNow(), }, } } - *reply = !*reply // Negate *reply to return true when lock is granted or false otherwise - return nil + // return reply=true if lock was granted. + return !isLockTaken, nil } -// Unlock - rpc handler for (single) write unlock operation. -func (l *lockServer) Unlock(args *LockArgs, reply *bool) error { +func (l *localLocker) Unlock(args dsync.LockArgs) (reply bool, err error) { l.mutex.Lock() defer l.mutex.Unlock() - if err := args.IsAuthenticated(); err != nil { - return err - } var lri []lockRequesterInfo - if lri, *reply = l.lockMap[args.LockArgs.Resource]; !*reply { // No lock is held on the given name - return fmt.Errorf("Unlock attempted on an unlocked entity: %s", args.LockArgs.Resource) + if lri, reply = l.lockMap[args.Resource]; !reply { + // No lock is held on the given name + return reply, fmt.Errorf("Unlock attempted on an unlocked entity: %s", args.Resource) } - if *reply = isWriteLock(lri); !*reply { // Unless it is a write lock - return fmt.Errorf("Unlock attempted on a read locked entity: %s (%d read locks active)", args.LockArgs.Resource, len(lri)) + if reply = isWriteLock(lri); !reply { + // Unless it is a write lock + return reply, fmt.Errorf("Unlock attempted on a read locked entity: %s (%d read locks active)", args.Resource, len(lri)) } - if !l.removeEntry(args.LockArgs.Resource, args.LockArgs.UID, &lri) { - return fmt.Errorf("Unlock unable to find corresponding lock for uid: %s", args.LockArgs.UID) + if !l.removeEntry(args.Resource, args.UID, &lri) { + return false, fmt.Errorf("Unlock unable to find corresponding lock for uid: %s", args.UID) } - return nil + return true, nil + } -// RLock - rpc handler for read lock operation. -func (l *lockServer) RLock(args *LockArgs, reply *bool) error { +func (l *localLocker) RLock(args dsync.LockArgs) (reply bool, err error) { l.mutex.Lock() defer l.mutex.Unlock() - if err := args.IsAuthenticated(); err != nil { - return err - } lrInfo := lockRequesterInfo{ writer: false, - node: args.LockArgs.ServerAddr, - serviceEndpoint: args.LockArgs.ServiceEndpoint, - uid: args.LockArgs.UID, + node: args.ServerAddr, + serviceEndpoint: args.ServiceEndpoint, + uid: args.UID, timestamp: UTCNow(), timeLastCheck: UTCNow(), } - if lri, ok := l.lockMap[args.LockArgs.Resource]; ok { - if *reply = !isWriteLock(lri); *reply { // Unless there is a write lock - l.lockMap[args.LockArgs.Resource] = append(l.lockMap[args.LockArgs.Resource], lrInfo) + if lri, ok := l.lockMap[args.Resource]; ok { + if reply = !isWriteLock(lri); reply { + // Unless there is a write lock + l.lockMap[args.Resource] = append(l.lockMap[args.Resource], lrInfo) } - } else { // No locks held on the given name, so claim (first) read lock - l.lockMap[args.LockArgs.Resource] = []lockRequesterInfo{lrInfo} - *reply = true + } else { + // No locks held on the given name, so claim (first) read lock + l.lockMap[args.Resource] = []lockRequesterInfo{lrInfo} + reply = true } - return nil + return reply, nil +} + +func (l *localLocker) RUnlock(args dsync.LockArgs) (reply bool, err error) { + l.mutex.Lock() + defer l.mutex.Unlock() + var lri []lockRequesterInfo + if lri, reply = l.lockMap[args.Resource]; !reply { + // No lock is held on the given name + return reply, fmt.Errorf("RUnlock attempted on an unlocked entity: %s", args.Resource) + } + if reply = !isWriteLock(lri); !reply { + // A write-lock is held, cannot release a read lock + return reply, fmt.Errorf("RUnlock attempted on a write locked entity: %s", args.Resource) + } + if !l.removeEntry(args.Resource, args.UID, &lri) { + return false, fmt.Errorf("RUnlock unable to find corresponding read lock for uid: %s", args.UID) + } + return reply, nil +} + +func (l *localLocker) ForceUnlock(args dsync.LockArgs) (reply bool, err error) { + l.mutex.Lock() + defer l.mutex.Unlock() + if len(args.UID) != 0 { + return false, fmt.Errorf("ForceUnlock called with non-empty UID: %s", args.UID) + } + if _, ok := l.lockMap[args.Resource]; ok { + // Only clear lock when it is taken + // Remove the lock (irrespective of write or read lock) + delete(l.lockMap, args.Resource) + } + return true, nil +} + +/// Distributed lock handlers + +// Lock - rpc handler for (single) write lock operation. +func (l *lockServer) Lock(args *LockArgs, reply *bool) (err error) { + if err = args.IsAuthenticated(); err != nil { + return err + } + *reply, err = l.ll.Lock(args.LockArgs) + return err +} + +// Unlock - rpc handler for (single) write unlock operation. +func (l *lockServer) Unlock(args *LockArgs, reply *bool) (err error) { + if err = args.IsAuthenticated(); err != nil { + return err + } + *reply, err = l.ll.Unlock(args.LockArgs) + return err +} + +// RLock - rpc handler for read lock operation. +func (l *lockServer) RLock(args *LockArgs, reply *bool) (err error) { + if err = args.IsAuthenticated(); err != nil { + return err + } + *reply, err = l.ll.RLock(args.LockArgs) + return err } // RUnlock - rpc handler for read unlock operation. -func (l *lockServer) RUnlock(args *LockArgs, reply *bool) error { - l.mutex.Lock() - defer l.mutex.Unlock() - if err := args.IsAuthenticated(); err != nil { +func (l *lockServer) RUnlock(args *LockArgs, reply *bool) (err error) { + if err = args.IsAuthenticated(); err != nil { return err } - var lri []lockRequesterInfo - if lri, *reply = l.lockMap[args.LockArgs.Resource]; !*reply { // No lock is held on the given name - return fmt.Errorf("RUnlock attempted on an unlocked entity: %s", args.LockArgs.Resource) - } - if *reply = !isWriteLock(lri); !*reply { // A write-lock is held, cannot release a read lock - return fmt.Errorf("RUnlock attempted on a write locked entity: %s", args.LockArgs.Resource) - } - if !l.removeEntry(args.LockArgs.Resource, args.LockArgs.UID, &lri) { - return fmt.Errorf("RUnlock unable to find corresponding read lock for uid: %s", args.LockArgs.UID) - } - return nil + *reply, err = l.ll.RUnlock(args.LockArgs) + return err } // ForceUnlock - rpc handler for force unlock operation. -func (l *lockServer) ForceUnlock(args *LockArgs, reply *bool) error { - l.mutex.Lock() - defer l.mutex.Unlock() - if err := args.IsAuthenticated(); err != nil { +func (l *lockServer) ForceUnlock(args *LockArgs, reply *bool) (err error) { + if err = args.IsAuthenticated(); err != nil { return err } - if len(args.LockArgs.UID) != 0 { - return fmt.Errorf("ForceUnlock called with non-empty UID: %s", args.LockArgs.UID) - } - if _, ok := l.lockMap[args.LockArgs.Resource]; ok { // Only clear lock when set - delete(l.lockMap, args.LockArgs.Resource) // Remove the lock (irrespective of write or read lock) - } - *reply = true - return nil + *reply, err = l.ll.ForceUnlock(args.LockArgs) + return err } // Expired - rpc handler for expired lock status. func (l *lockServer) Expired(args *LockArgs, reply *bool) error { - l.mutex.Lock() - defer l.mutex.Unlock() if err := args.IsAuthenticated(); err != nil { return err } + l.ll.mutex.Lock() + defer l.ll.mutex.Unlock() // Lock found, proceed to verify if belongs to given uid. - if lri, ok := l.lockMap[args.LockArgs.Resource]; ok { + if lri, ok := l.ll.lockMap[args.LockArgs.Resource]; ok { // Check whether uid is still active for _, entry := range lri { if entry.uid == args.LockArgs.UID { @@ -277,10 +305,10 @@ type nameLockRequesterInfoPair struct { // // We will ignore the error, and we will retry later to get a resolve on this lock func (l *lockServer) lockMaintenance(interval time.Duration) { - l.mutex.Lock() + l.ll.mutex.Lock() // Get list of long lived locks to check for staleness. - nlripLongLived := getLongLivedLocks(l.lockMap, interval) - l.mutex.Unlock() + nlripLongLived := getLongLivedLocks(l.ll.lockMap, interval) + l.ll.mutex.Unlock() serverCred := serverConfig.GetCredential() // Validate if long lived locks are indeed clean. @@ -308,9 +336,9 @@ func (l *lockServer) lockMaintenance(interval time.Duration) { if expired { // The lock is no longer active at server that originated the lock // So remove the lock from the map. - l.mutex.Lock() - l.removeEntryIfExists(nlrip) // Purge the stale entry if it exists. - l.mutex.Unlock() + l.ll.mutex.Lock() + l.ll.removeEntryIfExists(nlrip) // Purge the stale entry if it exists. + l.ll.mutex.Unlock() } } } diff --git a/cmd/lock-rpc-server_test.go b/cmd/lock-rpc-server_test.go index 2e51783c2..7ebf2621f 100644 --- a/cmd/lock-rpc-server_test.go +++ b/cmd/lock-rpc-server_test.go @@ -49,10 +49,12 @@ func createLockTestServer(t *testing.T) (string, *lockServer, string) { } locker := &lockServer{ - AuthRPCServer: AuthRPCServer{}, - serviceEndpoint: "rpc-path", - mutex: sync.Mutex{}, - lockMap: make(map[string][]lockRequesterInfo), + AuthRPCServer: AuthRPCServer{}, + ll: localLocker{ + mutex: sync.Mutex{}, + serviceEndpoint: "rpc-path", + lockMap: make(map[string][]lockRequesterInfo), + }, } creds := serverConfig.GetCredential() loginArgs := LoginRPCArgs{ @@ -93,7 +95,7 @@ func TestLockRpcServerLock(t *testing.T) { if !result { t.Errorf("Expected %#v, got %#v", true, result) } else { - gotLri, _ := locker.lockMap["name"] + gotLri, _ := locker.ll.lockMap["name"] expectedLri := []lockRequesterInfo{ { writer: true, @@ -163,7 +165,7 @@ func TestLockRpcServerUnlock(t *testing.T) { if !result { t.Errorf("Expected %#v, got %#v", true, result) } else { - gotLri, _ := locker.lockMap["name"] + gotLri, _ := locker.ll.lockMap["name"] expectedLri := []lockRequesterInfo(nil) if !testLockEquality(expectedLri, gotLri) { t.Errorf("Expected %#v, got %#v", expectedLri, gotLri) @@ -194,7 +196,7 @@ func TestLockRpcServerRLock(t *testing.T) { if !result { t.Errorf("Expected %#v, got %#v", true, result) } else { - gotLri, _ := locker.lockMap["name"] + gotLri, _ := locker.ll.lockMap["name"] expectedLri := []lockRequesterInfo{ { writer: false, @@ -281,7 +283,7 @@ func TestLockRpcServerRUnlock(t *testing.T) { if !result { t.Errorf("Expected %#v, got %#v", true, result) } else { - gotLri, _ := locker.lockMap["name"] + gotLri, _ := locker.ll.lockMap["name"] expectedLri := []lockRequesterInfo{ { writer: false, @@ -305,7 +307,7 @@ func TestLockRpcServerRUnlock(t *testing.T) { if !result { t.Errorf("Expected %#v, got %#v", true, result) } else { - gotLri, _ := locker.lockMap["name"] + gotLri, _ := locker.ll.lockMap["name"] expectedLri := []lockRequesterInfo(nil) if !testLockEquality(expectedLri, gotLri) { t.Errorf("Expected %#v, got %#v", expectedLri, gotLri) @@ -427,6 +429,12 @@ func TestLockServers(t *testing.T) { return } + rootPath, err := newTestConfig(globalMinioDefaultRegion) + if err != nil { + t.Fatalf("Init Test config failed") + } + defer removeAll(rootPath) + currentIsDistXL := globalIsDistXL defer func() { globalIsDistXL = currentIsDistXL @@ -471,9 +479,13 @@ func TestLockServers(t *testing.T) { // Validates lock server initialization. for i, testCase := range testCases { globalIsDistXL = testCase.isDistXL - lockServers := newLockServers(testCase.endpoints) - if len(lockServers) != testCase.totalLockServers { - t.Fatalf("Test %d: Expected total %d, got %d", i+1, testCase.totalLockServers, len(lockServers)) + globalLockServers = nil + _, _ = newDsyncNodes(testCase.endpoints) + if err != nil { + t.Fatalf("Got unexpected error initializing lock servers: %v", err) + } + if len(globalLockServers) != testCase.totalLockServers { + t.Fatalf("Test %d: Expected total %d, got %d", i+1, testCase.totalLockServers, len(globalLockServers)) } } } diff --git a/cmd/namespace-lock.go b/cmd/namespace-lock.go index 9aa470825..04ae2a2a9 100644 --- a/cmd/namespace-lock.go +++ b/cmd/namespace-lock.go @@ -27,6 +27,9 @@ import ( // Global name space lock. var globalNSMutex *nsLockMap +// Global lock servers +var globalLockServers []*lockServer + // RWLocker - locker interface extends sync.Locker // to introduce RLock, RUnlock. type RWLocker interface { @@ -36,27 +39,45 @@ type RWLocker interface { } // Initialize distributed locking only in case of distributed setup. -// Returns if the setup is distributed or not on success. -func initDsyncNodes() error { +// Returns lock clients and the node index for the current server. +func newDsyncNodes(endpoints EndpointList) (clnts []dsync.NetLocker, myNode int) { cred := serverConfig.GetCredential() - // Initialize rpc lock client information only if this instance is a distributed setup. - clnts := make([]dsync.NetLocker, len(globalEndpoints)) - myNode := -1 - for index, endpoint := range globalEndpoints { - clnts[index] = newLockRPCClient(authConfig{ - accessKey: cred.AccessKey, - secretKey: cred.SecretKey, - serverAddr: endpoint.Host, - secureConn: globalIsSSL, - serviceEndpoint: pathutil.Join(minioReservedBucketPath, lockServicePath, endpoint.Path), - serviceName: lockServiceName, - }) - if endpoint.IsLocal && myNode == -1 { + clnts = make([]dsync.NetLocker, len(endpoints)) + myNode = -1 + for index, endpoint := range endpoints { + if !endpoint.IsLocal { + // For a remote endpoints setup a lock RPC client. + clnts[index] = newLockRPCClient(authConfig{ + accessKey: cred.AccessKey, + secretKey: cred.SecretKey, + serverAddr: endpoint.Host, + secureConn: globalIsSSL, + serviceEndpoint: pathutil.Join(minioReservedBucketPath, lockServicePath, endpoint.Path), + serviceName: lockServiceName, + }) + continue + } + + // Local endpoint + if myNode == -1 { myNode = index } + // For a local endpoint, setup a local lock server to + // avoid network requests. + localLockServer := lockServer{ + AuthRPCServer: AuthRPCServer{}, + ll: localLocker{ + mutex: sync.Mutex{}, + serviceEndpoint: endpoint.Path, + serverAddr: endpoint.Host, + lockMap: make(map[string][]lockRequesterInfo), + }, + } + globalLockServers = append(globalLockServers, &localLockServer) + clnts[index] = &(localLockServer.ll) } - return dsync.Init(clnts, myNode) + return clnts, myNode } // initNSLock - initialize name space lock map. diff --git a/cmd/server-main.go b/cmd/server-main.go index 0d44cedfa..1a3157ca7 100644 --- a/cmd/server-main.go +++ b/cmd/server-main.go @@ -25,6 +25,7 @@ import ( "time" "github.com/minio/cli" + "github.com/minio/dsync" ) var serverFlags = []cli.Flag{ @@ -244,7 +245,8 @@ func serverMain(ctx *cli.Context) { // Set nodes for dsync for distributed setup. if globalIsDistXL { - fatalIf(initDsyncNodes(), "Unable to initialize distributed locking clients") + clnts, myNode := newDsyncNodes(globalEndpoints) + fatalIf(dsync.Init(clnts, myNode), "Unable to initialize distributed locking clients") } // Initialize name space lock. diff --git a/cmd/server-mux_test.go b/cmd/server-mux_test.go index 838017414..6904fc0f8 100644 --- a/cmd/server-mux_test.go +++ b/cmd/server-mux_test.go @@ -340,10 +340,11 @@ func TestServerListenAndServePlain(t *testing.T) { } func TestServerListenAndServeTLS(t *testing.T) { - _, err := newTestConfig(globalMinioDefaultRegion) + rootPath, err := newTestConfig(globalMinioDefaultRegion) if err != nil { t.Fatalf("Init Test config failed") } + defer removeAll(rootPath) wait := make(chan struct{}) addr := net.JoinHostPort("127.0.0.1", getFreePort()) From 1c3f244fc52f9d27d0767d9cb4799020ac8dc815 Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Mon, 5 Jun 2017 15:18:03 -0700 Subject: [PATCH 58/80] creds: Secretkey should be generated upto 40 characters in length. (#4471) Current code allowed it wrongly to generate secret key upto 100 we should only use 100 as a value to validate but for generating it should be 40. Fixes #4470 --- cmd/credential.go | 34 +++++++++++++++++++++++++--------- cmd/credential_test.go | 3 +++ cmd/gateway-main.go | 5 +++-- cmd/server_test.go | 7 ------- 4 files changed, 31 insertions(+), 18 deletions(-) diff --git a/cmd/credential.go b/cmd/credential.go index 769707d84..d3ff0c618 100644 --- a/cmd/credential.go +++ b/cmd/credential.go @@ -25,19 +25,35 @@ import ( ) const ( - accessKeyMinLen = 5 - accessKeyMaxLen = 20 - secretKeyMinLen = 8 - secretKeyMaxLenAmazon = 100 - alphaNumericTable = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" - alphaNumericTableLen = byte(len(alphaNumericTable)) + // Minimum length for Minio access key. + accessKeyMinLen = 5 + + // Maximum length for Minio access key. + accessKeyMaxLen = 20 + + // Minimum length for Minio secret key for both server and gateway mode. + secretKeyMinLen = 8 + + // Maximum secret key length for Minio, this + // is used when autogenerating new credentials. + secretKeyMaxLenMinio = 40 + + // Maximum secret key length allowed from client side + // caters for both server and gateway mode. + secretKeyMaxLen = 100 + + // Alpha numeric table used for generating access keys. + alphaNumericTable = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" + + // Total length of the alpha numeric table. + alphaNumericTableLen = byte(len(alphaNumericTable)) ) +// Common errors generated for access and secret key validation. 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 100 characters in length") ) -var secretKeyMaxLen = secretKeyMaxLenAmazon // isAccessKeyValid - validate access key for right length. func isAccessKeyValid(accessKey string) bool { @@ -111,10 +127,10 @@ func mustGetNewCredential() credential { accessKey := string(keyBytes) // Generate secret key. - keyBytes = make([]byte, secretKeyMaxLen) + keyBytes = make([]byte, secretKeyMaxLenMinio) _, err = rand.Read(keyBytes) fatalIf(err, "Unable to generate secret key.") - secretKey := string([]byte(base64.StdEncoding.EncodeToString(keyBytes))[:secretKeyMaxLen]) + secretKey := string([]byte(base64.StdEncoding.EncodeToString(keyBytes))[:secretKeyMaxLenMinio]) cred, err := createCredential(accessKey, secretKey) fatalIf(err, "Unable to generate new credential.") diff --git a/cmd/credential_test.go b/cmd/credential_test.go index b35d703de..ebf19e787 100644 --- a/cmd/credential_test.go +++ b/cmd/credential_test.go @@ -23,6 +23,9 @@ func TestMustGetNewCredential(t *testing.T) { if !cred.IsValid() { t.Fatalf("Failed to get new valid credential") } + if len(cred.SecretKey) != secretKeyMaxLenMinio { + t.Fatalf("Invalid length %d of the secretKey credential generated, expected %d", len(cred.SecretKey), secretKeyMaxLenMinio) + } } func TestCreateCredential(t *testing.T) { diff --git a/cmd/gateway-main.go b/cmd/gateway-main.go index 254d02ec9..eb896d337 100644 --- a/cmd/gateway-main.go +++ b/cmd/gateway-main.go @@ -19,11 +19,12 @@ package cmd import ( "errors" "fmt" - "github.com/gorilla/mux" - "github.com/minio/cli" "net/url" "os" "strings" + + "github.com/gorilla/mux" + "github.com/minio/cli" ) var gatewayTemplate = `NAME: diff --git a/cmd/server_test.go b/cmd/server_test.go index 8f607c142..097f1f9cf 100644 --- a/cmd/server_test.go +++ b/cmd/server_test.go @@ -93,13 +93,6 @@ func (s *TestSuiteCommon) TearDownSuite(c *C) { s.testServer.Stop() } -func (s *TestSuiteCommon) TestAuth(c *C) { - cred := mustGetNewCredential() - - c.Assert(len(cred.AccessKey), Equals, accessKeyMaxLen) - c.Assert(len(cred.SecretKey), Equals, secretKeyMaxLen) -} - func (s *TestSuiteCommon) TestBucketSQSNotificationWebHook(c *C) { // Sample bucket notification. bucketNotificationBuf := `s3:ObjectCreated:Putprefiximages/1arn:minio:sqs:us-east-1:444455556666:webhook` From f99987e47c57db9f343838d51851fe429e3658c5 Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Tue, 6 Jun 2017 11:25:06 -0700 Subject: [PATCH 59/80] Generate sha1sum as well for release for backward compatibility. (#4475) Additionally remove support for arm6vl in release, since go 1.8 the support for armv6 has been dropped and we do not see high usage events from this platform. --- buildscripts/build.sh | 74 +++++++++++++------------------------------ 1 file changed, 22 insertions(+), 52 deletions(-) diff --git a/buildscripts/build.sh b/buildscripts/build.sh index 15421e5db..849e3565f 100755 --- a/buildscripts/build.sh +++ b/buildscripts/build.sh @@ -28,6 +28,7 @@ _init() { ## System binaries CP=`which cp` SHASUM=`which shasum` + SHA256SUM="${SHASUM} -a 256" SED=`which sed` } @@ -43,67 +44,36 @@ go_build() { release_bin="$release_str/$os-$arch/$(basename $package).$release_tag" # Release binary downloadable name release_real_bin="$release_str/$os-$arch/$(basename $package)" + + # Release sha1sum name + release_shasum="$release_str/$os-$arch/$(basename $package).${release_tag}.shasum" + # Release sha1sum default + release_shasum_default="$release_str/$os-$arch/$(basename $package).shasum" + # Release sha256sum name release_sha256sum="$release_str/$os-$arch/$(basename $package).${release_tag}.sha256sum" # Release sha256sum default release_sha256sum_default="$release_str/$os-$arch/$(basename $package).sha256sum" # Go build to build the binary. - if [ "${arch}" == "arm" ]; then - # Release binary downloadable name - release_real_bin_6="$release_str/$os-${arch}6vl/$(basename $package)" + CGO_ENABLED=0 GOOS=$os GOARCH=$arch go build --ldflags "${LDFLAGS}" -o $release_bin - release_bin_6="$release_str/$os-${arch}6vl/$(basename $package).$release_tag" - ## Support building for ARM6vl - GOARM=6 GOOS=$os GOARCH=$arch go build --ldflags "${LDFLAGS}" -o $release_bin_6 - - ## Copy - $CP -p $release_bin_6 $release_real_bin_6 - - # Release sha256sum name - release_sha256sum_6="$release_str/$os-${arch}6vl/$(basename $package).${release_tag}.sha256sum" - # Release sha256sum default - release_sha256sum_default_6="$release_str/$os-${arch}6vl/$(basename $package).sha256sum" - - # Calculate shasum - shasum_str=$(${SHASUM} -a 256 ${release_bin_6}) - echo ${shasum_str} | $SED "s/$release_str\/$os-${arch}6vl\///g" > $release_sha256sum_6 - $CP -p $release_sha256sum_6 $release_sha256sum_default_6 - - # Release binary downloadable name - release_real_bin_7="$release_str/$os-$arch/$(basename $package)" - - release_bin_7="$release_str/$os-$arch/$(basename $package).$release_tag" - ## Support building for ARM7vl - GOARM=7 GOOS=$os GOARCH=$arch go build --ldflags "${LDFLAGS}" -o $release_bin_7 - - ## Copy - $CP -p $release_bin_7 $release_real_bin_7 - - # Release sha256sum name - release_sha256sum_7="$release_str/$os-$arch/$(basename $package).${release_tag}.sha256sum" - # Release sha256sum default - release_sha256sum_default_7="$release_str/$os-${arch}/$(basename $package).sha256sum" - - # Calculate sha256sum - shasum_str=$(${SHASUM} -a 256 ${release_bin_7}) - echo ${shasum_str} | $SED "s/$release_str\/$os-$arch\///g" > $release_sha256sum_7 - $CP -p $release_sha256sum_7 $release_sha256sum_default_7 + # Create copy + if [ $os == "windows" ]; then + $CP -p $release_bin ${release_real_bin}.exe else - GOOS=$os GOARCH=$arch go build --ldflags "${LDFLAGS}" -o $release_bin - - # Create copy - if [ $os == "windows" ]; then - $CP -p $release_bin ${release_real_bin}.exe - else - $CP -p $release_bin $release_real_bin - fi - - # Calculate shasum - sha256sum_str=$(${SHASUM} -a 256 ${release_bin}) - echo ${sha256sum_str} | $SED "s/$release_str\/$os-$arch\///g" > $release_sha256sum - $CP -p $release_sha256sum $release_sha256sum_default + $CP -p $release_bin $release_real_bin fi + + # Calculate sha1sum + shasum_str=$(${SHASUM} ${release_bin}) + echo ${shasum_str} | $SED "s/$release_str\/$os-$arch\///g" > $release_shasum + $CP -p $release_shasum $release_shasum_default + + # Calculate sha256sum + sha256sum_str=$(${SHA256SUM} ${release_bin}) + echo ${sha256sum_str} | $SED "s/$release_str\/$os-$arch\///g" > $release_sha256sum + $CP -p $release_sha256sum $release_sha256sum_default } main() { From 976870a39100ecc10d12ef163deedc1575d66b67 Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Tue, 6 Jun 2017 12:15:35 -0700 Subject: [PATCH 60/80] fs: Migration should handle bucketConfigs as regular objects. (#4482) Current code failed to anticipate the existence of files which could have been created to corrupt the namespace such as `policy.json` file created at the bucket top level. In the current release creating such as file conflicts with the namespace for future bucket policy operations. We implemented migration of backend format to avoid situations such as these. This PR handles this situation, makes sure that the erroneous files should have been moved properly. Fixes #4478 --- cmd/fs-v1-helpers.go | 49 ++++++++++--------- cmd/fs-v1.go | 43 +++++++++++++---- cmd/fs-v1_test.go | 110 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 169 insertions(+), 33 deletions(-) diff --git a/cmd/fs-v1-helpers.go b/cmd/fs-v1-helpers.go index 47dbb160f..002eda887 100644 --- a/cmd/fs-v1-helpers.go +++ b/cmd/fs-v1-helpers.go @@ -123,24 +123,31 @@ func fsMkdir(dirPath string) (err error) { return nil } +func fsStat(statLoc string) (os.FileInfo, error) { + if statLoc == "" { + return nil, traceError(errInvalidArgument) + } + if err := checkPathLength(statLoc); err != nil { + return nil, traceError(err) + } + fi, err := osStat(preparePath(statLoc)) + if err != nil { + return nil, traceError(err) + } + return fi, nil +} + // Lookup if directory exists, returns directory // attributes upon success. func fsStatDir(statDir string) (os.FileInfo, error) { - if statDir == "" { - return nil, traceError(errInvalidArgument) - } - if err := checkPathLength(statDir); err != nil { - return nil, traceError(err) - } - - fi, err := osStat(preparePath(statDir)) + fi, err := fsStat(statDir) if err != nil { - if os.IsNotExist(err) { + if os.IsNotExist(errorCause(err)) { return nil, traceError(errVolumeNotFound) - } else if os.IsPermission(err) { + } else if os.IsPermission(errorCause(err)) { return nil, traceError(errVolumeAccessDenied) } - return nil, traceError(err) + return nil, err } if !fi.IsDir() { @@ -152,26 +159,18 @@ func fsStatDir(statDir string) (os.FileInfo, error) { // Lookup if file exists, returns file attributes upon success func fsStatFile(statFile string) (os.FileInfo, error) { - if statFile == "" { - return nil, traceError(errInvalidArgument) - } - - if err := checkPathLength(statFile); err != nil { - return nil, traceError(err) - } - - fi, err := osStat(preparePath(statFile)) + fi, err := fsStat(statFile) if err != nil { - if os.IsNotExist(err) { + if os.IsNotExist(errorCause(err)) { return nil, traceError(errFileNotFound) - } else if os.IsPermission(err) { + } else if os.IsPermission(errorCause(err)) { return nil, traceError(errFileAccessDenied) - } else if isSysErrNotDir(err) { + } else if isSysErrNotDir(errorCause(err)) { return nil, traceError(errFileAccessDenied) - } else if isSysErrPathNotFound(err) { + } else if isSysErrPathNotFound(errorCause(err)) { return nil, traceError(errFileNotFound) } - return nil, traceError(err) + return nil, err } if fi.IsDir() { return nil, traceError(errFileAccessDenied) diff --git a/cmd/fs-v1.go b/cmd/fs-v1.go index 590b7d1ad..58c8b8b85 100644 --- a/cmd/fs-v1.go +++ b/cmd/fs-v1.go @@ -113,12 +113,43 @@ func fsReaddirMetaBuckets(fsPath string) ([]string, error) { return f.Readdirnames(-1) } +// List of all bucket metadata configs. var bucketMetadataConfigs = []string{ bucketNotificationConfig, bucketListenerConfig, bucketPolicyConfig, } +// Migrates bucket metadata configs, ignores all other files. +func migrateBucketMetadataConfigs(metaBucket, bucket, tmpBucket string) error { + for _, bucketMetaFile := range bucketMetadataConfigs { + fi, err := fsStat(pathJoin(metaBucket, tmpBucket, bucketMetaFile)) + if err != nil { + // There are no such files or directories found, + // proceed to next bucket metadata config. + if os.IsNotExist(errorCause(err)) { + continue + } + return err + } + + // Bucket metadata is a file, move it as an actual bucket config. + if fi.Mode().IsRegular() { + if err = fsRenameFile(pathJoin(metaBucket, tmpBucket, bucketMetaFile), + pathJoin(metaBucket, bucket, bucketMetaFile)); err != nil { + if errorCause(err) != errFileNotFound { + return err + } + } + } + + // All other file types are ignored. + } + + // Success. + return nil +} + // Attempts to migrate old object metadata files to newer format // // i.e @@ -152,14 +183,9 @@ func migrateFSFormatV1ToV2(fsPath, fsUUID string) (err error) { return err } - /// Rename all bucket metadata files to newly created `bucket`. - for _, bucketMetaFile := range bucketMetadataConfigs { - if err = fsRenameFile(pathJoin(metaBucket, tmpBucket, bucketMetaFile), - pathJoin(metaBucket, bucket, bucketMetaFile)); err != nil { - if errorCause(err) != errFileNotFound { - return err - } - } + // Migrate all the bucket metadata configs. + if err = migrateBucketMetadataConfigs(metaBucket, bucket, tmpBucket); err != nil { + return err } // Finally rename the temporary bucket to `bucket/objects` directory. @@ -169,6 +195,7 @@ func migrateFSFormatV1ToV2(fsPath, fsUUID string) (err error) { return err } } + } log.Printf("Migrating bucket metadata format from \"%s\" to newer format \"%s\"... completed successfully.", fsFormatV1, fsFormatV2) diff --git a/cmd/fs-v1_test.go b/cmd/fs-v1_test.go index a79f0327b..6c2a4b6a1 100644 --- a/cmd/fs-v1_test.go +++ b/cmd/fs-v1_test.go @@ -207,6 +207,116 @@ func TestFSMigrateObjectWithErr(t *testing.T) { } +// Tests migrating FS format with .minio.sys/buckets filled with +// objects such as policy.json/fs.json, notification.xml/fs.json +// listener.json/fs.json. +func TestFSMigrateObjectWithBucketConfigObjects(t *testing.T) { + // Prepare for testing + disk := filepath.Join(globalTestTmpDir, "minio-"+nextSuffix()) + defer removeAll(disk) + + // Assign a new UUID. + uuid := mustGetUUID() + + // Initialize meta volume, if volume already exists ignores it. + if err := initMetaVolumeFS(disk, uuid); err != nil { + t.Fatal(err) + } + + fsFormatPath := pathJoin(disk, minioMetaBucket, fsFormatJSONFile) + formatCfg := &formatConfigV1{ + Version: "1", + Format: "fs", + FS: &fsFormat{ + Version: "1", + }, + } + lk, err := lock.LockedOpenFile(preparePath(fsFormatPath), os.O_RDWR|os.O_CREATE, 0600) + if err != nil { + t.Fatal(err) + } + _, err = formatCfg.WriteTo(lk) + lk.Close() + if err != nil { + t.Fatal("Should not fail here", err) + } + + // Construct the full path of fs.json + fsPath1 := pathJoin(bucketMetaPrefix, "testvolume1", bucketPolicyConfig, fsMetaJSONFile) + fsPath1 = pathJoin(disk, minioMetaBucket, fsPath1) + + fsMetaJSON := `{"version":"1.0.0","format":"fs","minio":{"release":"DEVELOPMENT.2017-03-27T02-26-33Z"},"meta":{"etag":"467886be95c8ecfd71a2900e3f461b4f"}` + if _, err = fsCreateFile(fsPath1, bytes.NewReader([]byte(fsMetaJSON)), nil, 0); err != nil { + t.Fatal(err) + } + + // Construct the full path of fs.json + fsPath2 := pathJoin(bucketMetaPrefix, "testvolume2", bucketNotificationConfig, fsMetaJSONFile) + fsPath2 = pathJoin(disk, minioMetaBucket, fsPath2) + + fsMetaJSON = `{"version":"1.0.0","format":"fs","minio":{"release":"DEVELOPMENT.2017-03-27T02-26-33Z"},"meta":{"etag":"467886be95c8ecfd71a2900eff461b4d"}` + if _, err = fsCreateFile(fsPath2, bytes.NewReader([]byte(fsMetaJSON)), nil, 0); err != nil { + t.Fatal(err) + } + + // Construct the full path of fs.json + fsPath3 := pathJoin(bucketMetaPrefix, "testvolume3", bucketListenerConfig, fsMetaJSONFile) + fsPath3 = pathJoin(disk, minioMetaBucket, fsPath3) + + fsMetaJSON = `{"version":"1.0.0","format":"fs","minio":{"release":"DEVELOPMENT.2017-03-27T02-26-33Z"},"meta":{"etag":"467886be95c8ecfd71a2900eff461b4d"}` + if _, err = fsCreateFile(fsPath3, bytes.NewReader([]byte(fsMetaJSON)), nil, 0); err != nil { + t.Fatal(err) + } + + if err = initFormatFS(disk, mustGetUUID()); err != nil { + t.Fatal("Should not fail here", err) + } + + fsPath1 = pathJoin(bucketMetaPrefix, "testvolume1", objectMetaPrefix, bucketPolicyConfig, fsMetaJSONFile) + fsPath1 = pathJoin(disk, minioMetaBucket, fsPath1) + fi, err := fsStatFile(fsPath1) + if err != nil { + t.Fatal("Path should exist and accessible after migration", err) + } + if fi.IsDir() { + t.Fatalf("Unexpected path %s should be a file", fsPath1) + } + + fsPath2 = pathJoin(bucketMetaPrefix, "testvolume2", objectMetaPrefix, bucketNotificationConfig, fsMetaJSONFile) + fsPath2 = pathJoin(disk, minioMetaBucket, fsPath2) + fi, err = fsStatFile(fsPath2) + if err != nil { + t.Fatal("Path should exist and accessible after migration", err) + } + if fi.IsDir() { + t.Fatalf("Unexpected path %s should be a file", fsPath2) + } + + fsPath3 = pathJoin(bucketMetaPrefix, "testvolume3", objectMetaPrefix, bucketListenerConfig, fsMetaJSONFile) + fsPath3 = pathJoin(disk, minioMetaBucket, fsPath3) + fi, err = fsStatFile(fsPath3) + if err != nil { + t.Fatal("Path should exist and accessible after migration", err) + } + if fi.IsDir() { + t.Fatalf("Unexpected path %s should be a file", fsPath3) + } + + formatCfg = &formatConfigV1{} + lk, err = lock.LockedOpenFile(preparePath(fsFormatPath), os.O_RDONLY, 0600) + if err != nil { + t.Fatal(err) + } + _, err = formatCfg.ReadFrom(lk) + lk.Close() + if err != nil { + t.Fatal("Should not fail here", err) + } + if formatCfg.FS.Version != fsFormatV2 { + t.Fatalf("Unexpected version detected expected \"%s\", got %s", fsFormatV2, formatCfg.FS.Version) + } +} + // Tests migrating FS format with .minio.sys/buckets filled with // object metadata. func TestFSMigrateObjectWithObjects(t *testing.T) { From 6651c2fc5f21dcd55782555761f82303b732e772 Mon Sep 17 00:00:00 2001 From: poornas Date: Tue, 6 Jun 2017 14:56:41 -0700 Subject: [PATCH 61/80] disable settings change on browser in gateway mode (#4472) --- cmd/gateway-main.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmd/gateway-main.go b/cmd/gateway-main.go index eb896d337..667869cac 100644 --- a/cmd/gateway-main.go +++ b/cmd/gateway-main.go @@ -234,6 +234,9 @@ func gatewayMain(ctx *cli.Context) { router := mux.NewRouter().SkipClean(true) + // credentials Envs are set globally. + globalIsEnvCreds = true + // Register web router when its enabled. if globalIsBrowserEnabled { aerr := registerWebRouter(router) From 999ae1cb965c9cc162adcdd7c79fcfcc44c125df Mon Sep 17 00:00:00 2001 From: poornas Date: Tue, 6 Jun 2017 18:19:35 -0700 Subject: [PATCH 62/80] Fix browser download returning zero bytes for s3 (#4483) --- cmd/gateway-s3.go | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/cmd/gateway-s3.go b/cmd/gateway-s3.go index 027b1071a..709153e1f 100644 --- a/cmd/gateway-s3.go +++ b/cmd/gateway-s3.go @@ -261,8 +261,15 @@ func fromMinioClientListBucketResult(bucket string, result minio.ListBucketResul // length indicates the total length of the object. func (l *s3Objects) GetObject(bucket string, key string, startOffset int64, length int64, writer io.Writer) error { r := minio.NewGetReqHeaders() - if err := r.SetRange(startOffset, startOffset+length-1); err != nil { - return s3ToObjectError(traceError(err), bucket, key) + + if length < 0 && length != -1 { + return s3ToObjectError(traceError(errInvalidArgument), bucket, key) + } + + if startOffset >= 0 && length >= 0 { + if err := r.SetRange(startOffset, startOffset+length-1); err != nil { + return s3ToObjectError(traceError(err), bucket, key) + } } object, _, err := l.Client.GetObject(bucket, key, r) if err != nil { @@ -271,10 +278,9 @@ func (l *s3Objects) GetObject(bucket string, key string, startOffset int64, leng defer object.Close() - if _, err := io.CopyN(writer, object, length); err != nil { + if _, err := io.Copy(writer, object); err != nil { return s3ToObjectError(traceError(err), bucket, key) } - return nil } From 7dcc1e92b48a9a15a67193ab1ac096fc666278a9 Mon Sep 17 00:00:00 2001 From: Frank Wessels Date: Wed, 7 Jun 2017 17:24:46 -0700 Subject: [PATCH 63/80] Prevent unnecessary (superfluous) initialization of return variable (#4490) --- cmd/bucket-notification-utils.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/cmd/bucket-notification-utils.go b/cmd/bucket-notification-utils.go index c643c69d2..de9275b09 100644 --- a/cmd/bucket-notification-utils.go +++ b/cmd/bucket-notification-utils.go @@ -259,15 +259,14 @@ func validateNotificationConfig(nConfig notificationConfig) APIErrorCode { // - kafka // - webhook func unmarshalSqsARN(queueARN string) (mSqs arnSQS) { - mSqs = arnSQS{} strs := strings.SplitN(queueARN, ":", -1) if len(strs) != 6 { - return mSqs + return } if serverConfig.GetRegion() != "" { region := strs[3] if region != serverConfig.GetRegion() { - return mSqs + return } } sqsType := strs[5] @@ -294,5 +293,5 @@ func unmarshalSqsARN(queueARN string) (mSqs arnSQS) { mSqs.AccountID = strs[4] - return mSqs + return } From 45a568dd8549abe6e2b8bc32bd0304a3ee72e48a Mon Sep 17 00:00:00 2001 From: poornas Date: Wed, 7 Jun 2017 19:31:23 -0700 Subject: [PATCH 64/80] Give more specific error message on browser for nested policies (#4488) --- cmd/web-handlers.go | 52 +++++++++++++++++++++++++--------------- cmd/web-handlers_test.go | 7 ++++++ 2 files changed, 40 insertions(+), 19 deletions(-) diff --git a/cmd/web-handlers.go b/cmd/web-handlers.go index 99f74333f..28c84d289 100644 --- a/cmd/web-handlers.go +++ b/cmd/web-handlers.go @@ -802,6 +802,13 @@ func (web *webAPIHandlers) SetBucketPolicy(r *http.Request, args *SetBucketPolic policyInfo.Statements = policy.SetPolicy(policyInfo.Statements, bucketP, args.BucketName, args.Prefix) switch g := objectAPI.(type) { case GatewayLayer: + if len(policyInfo.Statements) == 0 { + err = g.DeleteBucketPolicies(args.BucketName) + if err != nil { + return toJSONError(err, args.BucketName) + } + return nil + } err = g.SetBucketPolicies(args.BucketName, policyInfo) if err != nil { return toJSONError(err) @@ -1011,39 +1018,46 @@ func toWebAPIError(err error) APIError { } } // Convert error type to api error code. - var apiErrCode APIErrorCode switch err.(type) { case StorageFull: - apiErrCode = ErrStorageFull + return getAPIError(ErrStorageFull) case BucketNotFound: - apiErrCode = ErrNoSuchBucket + return getAPIError(ErrNoSuchBucket) case BucketExists: - apiErrCode = ErrBucketAlreadyOwnedByYou + return getAPIError(ErrBucketAlreadyOwnedByYou) case BucketNameInvalid: - apiErrCode = ErrInvalidBucketName + return getAPIError(ErrInvalidBucketName) case BadDigest: - apiErrCode = ErrBadDigest + return getAPIError(ErrBadDigest) case IncompleteBody: - apiErrCode = ErrIncompleteBody + return getAPIError(ErrIncompleteBody) case ObjectExistsAsDirectory: - apiErrCode = ErrObjectExistsAsDirectory + return getAPIError(ErrObjectExistsAsDirectory) case ObjectNotFound: - apiErrCode = ErrNoSuchKey + return getAPIError(ErrNoSuchKey) case ObjectNameInvalid: - apiErrCode = ErrNoSuchKey + return getAPIError(ErrNoSuchKey) case InsufficientWriteQuorum: - apiErrCode = ErrWriteQuorum + return getAPIError(ErrWriteQuorum) case InsufficientReadQuorum: - apiErrCode = ErrReadQuorum + return getAPIError(ErrReadQuorum) case PolicyNesting: - apiErrCode = ErrPolicyNesting - default: - // Log unexpected and unhandled errors. - errorIf(err, errUnexpected.Error()) - apiErrCode = ErrInternalError + return getAPIError(ErrPolicyNesting) + case NotImplemented: + return APIError{ + Code: "NotImplemented", + HTTPStatusCode: http.StatusBadRequest, + Description: "Functionality not implemented", + } + } + + // Log unexpected and unhandled errors. + errorIf(err, errUnexpected.Error()) + return APIError{ + Code: "InternalError", + HTTPStatusCode: http.StatusInternalServerError, + Description: err.Error(), } - apiErr := getAPIError(apiErrCode) - return apiErr } // writeWebErrorResponse - set HTTP status code and write error description to the body. diff --git a/cmd/web-handlers_test.go b/cmd/web-handlers_test.go index 13681b7e7..41dfab2a3 100644 --- a/cmd/web-handlers_test.go +++ b/cmd/web-handlers_test.go @@ -86,12 +86,19 @@ func TestWriteWebErrorResponse(t *testing.T) { webErr: InsufficientReadQuorum{}, apiErrCode: ErrReadQuorum, }, + { + webErr: NotImplemented{}, + apiErrCode: ErrNotImplemented, + }, } // Validate all the test cases. for i, testCase := range testCases { writeWebErrorResponse(newFlushWriter(&buffer), testCase.webErr) desc := getAPIError(testCase.apiErrCode).Description + if testCase.apiErrCode == ErrNotImplemented { + desc = "Functionality not implemented" + } recvDesc := buffer.Bytes() // Check if the written desc is same as the one expected. if !bytes.Equal(recvDesc, []byte(desc)) { From 145328ac9fd78e9bd33e4557b24671f07f784bc9 Mon Sep 17 00:00:00 2001 From: Frank Wessels Date: Thu, 8 Jun 2017 07:39:50 -0700 Subject: [PATCH 65/80] tests: Run select statement in separate goroutine (#4499) Instead of after the wg.Wait() so as to make sure that the 'earliest' of the two select case that becomes active is selected.. --- cmd/server-mux_test.go | 69 +++++++++++++++++++++++------------------- 1 file changed, 38 insertions(+), 31 deletions(-) diff --git a/cmd/server-mux_test.go b/cmd/server-mux_test.go index 6904fc0f8..31fb7f01a 100644 --- a/cmd/server-mux_test.go +++ b/cmd/server-mux_test.go @@ -309,34 +309,36 @@ func TestServerListenAndServePlain(t *testing.T) { // ListenAndServe in a goroutine, but we don't know when it's ready go func() { errc <- m.ListenAndServe("", "") }() - wg := &sync.WaitGroup{} - wg.Add(1) // Keep trying the server until it's accepting connections go func() { client := http.Client{Timeout: time.Millisecond * 10} - ok := false - for !ok { + for { res, _ := client.Get("http://" + addr) if res != nil && res.StatusCode == http.StatusOK { - ok = true + break } } - - wg.Done() }() + wg := &sync.WaitGroup{} + wg.Add(1) + go func() { + defer wg.Done() + select { + case err := <-errc: + if err != nil { + t.Fatal(err) + } + case <-wait: + return + } + }() + + // Wait until we get an error or wait closed wg.Wait() - // Block until we get an error or wait closed - select { - case err := <-errc: - if err != nil { - t.Fatal(err) - } - case <-wait: - m.Close() // Shutdown the ServerMux - return - } + // Shutdown the ServerMux + m.Close() } func TestServerListenAndServeTLS(t *testing.T) { @@ -389,12 +391,11 @@ func TestServerListenAndServeTLS(t *testing.T) { Timeout: time.Millisecond * 10, Transport: tr, } - okTLS := false // Keep trying the server until it's accepting connections - for !okTLS { + for { res, _ := client.Get("https://" + addr) if res != nil && res.StatusCode == http.StatusOK { - okTLS = true + break } } @@ -423,19 +424,25 @@ func TestServerListenAndServeTLS(t *testing.T) { wg.Done() }() - wg.Wait() - - // Block until we get an error or wait closed - select { - case err := <-errc: - if err != nil { - t.Error(err) + wg.Add(1) + go func() { + defer wg.Done() + select { + case err := <-errc: + if err != nil { + t.Error(err) + return + } + case <-wait: return } - case <-wait: - m.Close() // Shutdown the ServerMux - return - } + }() + + // Wait until we get an error or wait closed + wg.Wait() + + // Shutdown the ServerMux + m.Close() } // generateTestCert creates a cert and a key used for testing only From 2c56788f8d94e75c3b191a36e98904b2d1deb2bd Mon Sep 17 00:00:00 2001 From: Krishna Srinivas Date: Thu, 8 Jun 2017 11:20:56 -0700 Subject: [PATCH 66/80] Validate gateway arguments (#4376) Fixes #4355 --- cmd/gateway-main.go | 43 +++++++++++++++++++++++++++++++--------- cmd/gateway-main_test.go | 33 ++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 9 deletions(-) diff --git a/cmd/gateway-main.go b/cmd/gateway-main.go index 667869cac..82fc77711 100644 --- a/cmd/gateway-main.go +++ b/cmd/gateway-main.go @@ -21,6 +21,7 @@ import ( "fmt" "net/url" "os" + "runtime" "strings" "github.com/gorilla/mux" @@ -181,6 +182,36 @@ func parseGatewayEndpoint(arg string) (endPoint string, secure bool, err error) } } +// Validate gateway arguments. +func validateGatewayArguments(serverAddr, endpointAddr string) error { + if err := CheckLocalServerAddr(serverAddr); err != nil { + return err + } + + if runtime.GOOS == "darwin" { + _, port := mustSplitHostPort(serverAddr) + // On macOS, if a process already listens on LOCALIPADDR:PORT, net.Listen() falls back + // to IPv6 address i.e minio will start listening on IPv6 address whereas another + // (non-)minio process is listening on IPv4 of given port. + // To avoid this error situation we check for port availability only for macOS. + if err := checkPortAvailability(port); err != nil { + return err + } + } + + if endpointAddr != "" { + // Reject the endpoint if it points to the gateway handler itself. + sameTarget, err := sameLocalAddrs(endpointAddr, serverAddr) + if err != nil { + return err + } + if sameTarget { + return errors.New("endpoint points to the local gateway") + } + } + return nil +} + // Handler for 'minio gateway'. func gatewayMain(ctx *cli.Context) { if !ctx.Args().Present() || ctx.Args().First() == "help" { @@ -207,17 +238,11 @@ func gatewayMain(ctx *cli.Context) { backendType := ctx.Args().Get(0) // Second argument is the endpoint address (optional) endpointAddr := ctx.Args().Get(1) - // Third argument is the address flag + serverAddr := ctx.String("address") - if endpointAddr != "" { - // Reject the endpoint if it points to the gateway handler itself. - sameTarget, err := sameLocalAddrs(endpointAddr, serverAddr) - fatalIf(err, "Unable to compare server and endpoint addresses.") - if sameTarget { - fatalIf(errors.New("endpoint points to the local gateway"), "Endpoint url is not allowed") - } - } + err := validateGatewayArguments(serverAddr, endpointAddr) + fatalIf(err, "Invalid argument") // Second argument is endpoint. If no endpoint is specified then the // gateway implementation should use a default setting. diff --git a/cmd/gateway-main_test.go b/cmd/gateway-main_test.go index 1779725bf..b80d0292b 100644 --- a/cmd/gateway-main_test.go +++ b/cmd/gateway-main_test.go @@ -18,6 +18,7 @@ package cmd import ( "os" + "strings" "testing" ) @@ -73,3 +74,35 @@ func TestSetBrowserFromEnv(t *testing.T) { } os.Setenv("MINIO_BROWSER", browser) } + +// Test validateGatewayArguments +func TestValidateGatewayArguments(t *testing.T) { + nonLoopBackIPs := localIP4.FuncMatch(func(ip string, matchString string) bool { + return !strings.HasPrefix(ip, "127.") + }, "") + if len(nonLoopBackIPs) == 0 { + t.Fatalf("No non-loop back IP address found for this host") + } + nonLoopBackIP := nonLoopBackIPs.ToSlice()[0] + + testCases := []struct { + serverAddr string + endpointAddr string + valid bool + }{ + {":9000", "http://localhost:9001", true}, + {":9000", "http://google.com", true}, + {"123.123.123.123:9000", "http://localhost:9000", false}, + {":9000", "http://localhost:9000", false}, + {":9000", nonLoopBackIP + ":9000", false}, + } + for i, test := range testCases { + err := validateGatewayArguments(test.serverAddr, test.endpointAddr) + if test.valid && err != nil { + t.Errorf("Test %d expected not to return error but got %s", i+1, err) + } + if !test.valid && err == nil { + t.Errorf("Test %d expected to fail but it did not", i+1) + } + } +} From 8a6b0cc0cd1f2c6624b23a15f21e2c4ca6248fa5 Mon Sep 17 00:00:00 2001 From: Krishnan Parthasarathi Date: Thu, 8 Jun 2017 19:08:21 +0000 Subject: [PATCH 67/80] TestInitListeners: Use port 0 pick available port (#4508) --- cmd/server-mux_test.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/cmd/server-mux_test.go b/cmd/server-mux_test.go index 31fb7f01a..614621b2f 100644 --- a/cmd/server-mux_test.go +++ b/cmd/server-mux_test.go @@ -109,20 +109,18 @@ func dial(addr string) error { // Tests initializing listeners. func TestInitListeners(t *testing.T) { - portTest1 := getFreePort() - portTest2 := getFreePort() testCases := []struct { serverAddr string shouldPass bool }{ // Test 1 with ip and port. { - serverAddr: "127.0.0.1:" + portTest1, + serverAddr: net.JoinHostPort("127.0.0.1", "0"), shouldPass: true, }, // Test 2 only port. { - serverAddr: ":" + portTest2, + serverAddr: net.JoinHostPort("", "0"), shouldPass: true, }, // Test 3 with no port error. From 0f5483f4978ac0d3e114abe13c9f60942e6332b1 Mon Sep 17 00:00:00 2001 From: Rushan Date: Fri, 9 Jun 2017 03:39:50 +0530 Subject: [PATCH 68/80] browser: Disable usage/free stats for browser-gateway (#4497) --- browser/app/js/components/Browse.js | 34 ++++++++++++++++------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/browser/app/js/components/Browse.js b/browser/app/js/components/Browse.js index 5b0accd29..df54cf34c 100644 --- a/browser/app/js/components/Browse.js +++ b/browser/app/js/components/Browse.js @@ -463,21 +463,25 @@ export default class Browse extends React.Component { } if (web.LoggedIn()) { - storageUsageDetails =
-
-
-
-
    -
  • - Used: - { humanize.filesize(total - free) } -
  • -
  • - Free: - { humanize.filesize(total - used) } -
  • -
-
+ if (!(used === 0 && free === 0)) { + storageUsageDetails =
+
+
+
+
    +
  • + Used: + { humanize.filesize(total - free) } +
  • +
  • + Free: + { humanize.filesize(total - used) } +
  • +
+
+ } + + } let createButton = '' From ec2920e9811484525c0e52a377774086d25382d7 Mon Sep 17 00:00:00 2001 From: Krishna Srinivas Date: Thu, 8 Jun 2017 18:58:51 -0700 Subject: [PATCH 69/80] Allow "minio server ." to start minio in fs mode (#4513) --- cmd/endpoint.go | 2 +- cmd/endpoint_test.go | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/cmd/endpoint.go b/cmd/endpoint.go index 30af75657..c42160efa 100644 --- a/cmd/endpoint.go +++ b/cmd/endpoint.go @@ -80,7 +80,7 @@ func (endpoint Endpoint) SetHTTP() { func NewEndpoint(arg string) (Endpoint, error) { // isEmptyPath - check whether given path is not empty. isEmptyPath := func(path string) bool { - return path == "" || path == "." || path == "/" || path == `\` + return path == "" || path == "/" || path == `\` } if isEmptyPath(arg) { diff --git a/cmd/endpoint_test.go b/cmd/endpoint_test.go index 49736bd66..c01fd7a6c 100644 --- a/cmd/endpoint_test.go +++ b/cmd/endpoint_test.go @@ -62,7 +62,6 @@ func TestNewEndpoint(t *testing.T) { {"http://127.0.0.1:8080/path", Endpoint{URL: u3, IsLocal: true}, URLEndpointType, nil}, {"http://192.168.253.200/path", Endpoint{URL: u4}, URLEndpointType, nil}, {"", Endpoint{}, -1, fmt.Errorf("empty or root endpoint is not supported")}, - {".", Endpoint{}, -1, fmt.Errorf("empty or root endpoint is not supported")}, {"/", Endpoint{}, -1, fmt.Errorf("empty or root endpoint is not supported")}, {`\`, Endpoint{}, -1, fmt.Errorf("empty or root endpoint is not supported")}, {"c://foo", Endpoint{}, -1, fmt.Errorf("invalid URL endpoint format")}, From 3dfe254a1112384c336d14734f2cb23366d92ab3 Mon Sep 17 00:00:00 2001 From: Bala FA Date: Fri, 9 Jun 2017 11:58:45 +0530 Subject: [PATCH 70/80] gateway: make each backend as subcommands. (#4506) Fixes #4450 --- cmd/gateway-main.go | 123 +++++++++++++++++++++++++++---------- cmd/gateway-startup-msg.go | 2 +- cmd/main.go | 8 ++- 3 files changed, 95 insertions(+), 38 deletions(-) diff --git a/cmd/gateway-main.go b/cmd/gateway-main.go index 82fc77711..adb2bf9d6 100644 --- a/cmd/gateway-main.go +++ b/cmd/gateway-main.go @@ -28,23 +28,22 @@ import ( "github.com/minio/cli" ) -var gatewayTemplate = `NAME: +const azureGatewayTemplate = `NAME: {{.HelpName}} - {{.Usage}} USAGE: - {{.HelpName}} {{if .VisibleFlags}}[FLAGS]{{end}} BACKEND [ENDPOINT] + {{.HelpName}} {{if .VisibleFlags}}[FLAGS]{{end}} [ENDPOINT] {{if .VisibleFlags}} FLAGS: {{range .VisibleFlags}}{{.}} {{end}}{{end}} -BACKEND: - azure: Microsoft Azure Blob Storage. Default ENDPOINT is https://core.windows.net - s3: Amazon Simple Storage Service (S3). Default ENDPOINT is https://s3.amazonaws.com +ENDPOINT: + Azure server endpoint. Default ENDPOINT is https://core.windows.net 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. + MINIO_ACCESS_KEY: Username or access key of Azure storage. + MINIO_SECRET_KEY: Password or secret key of Azure storage. BROWSER: MINIO_BROWSER: To disable web browser access, set this value to "off". @@ -53,24 +52,18 @@ EXAMPLES: 1. Start minio gateway server for Azure Blob Storage backend. $ export MINIO_ACCESS_KEY=azureaccountname $ export MINIO_SECRET_KEY=azureaccountkey - $ {{.HelpName}} azure - - 2. Start minio gateway server for AWS S3 backend. - $ export MINIO_ACCESS_KEY=accesskey - $ export MINIO_SECRET_KEY=secretkey - $ {{.HelpName}} s3 - - 3. Start minio gateway server for S3 backend on custom endpoint. - $ export MINIO_ACCESS_KEY=Q3AM3UQ867SPQQA43P2F - $ export MINIO_SECRET_KEY=zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG - $ {{.HelpName}} s3 https://play.minio.io:9000 + $ {{.HelpName}} + 2. Start minio gateway server for Azure Blob Storage backend on custom endpoint. + $ export MINIO_ACCESS_KEY=azureaccountname + $ export MINIO_SECRET_KEY=azureaccountkey + $ {{.HelpName}} https://azure.example.com ` -var gatewayCmd = cli.Command{ - Name: "gateway", - Usage: "Start object storage gateway.", - Action: gatewayMain, - CustomHelpTemplate: gatewayTemplate, +var azureBackendCmd = cli.Command{ + Name: "azure", + Usage: "Microsoft Azure Blob Storage.", + Action: azureGatewayMain, + CustomHelpTemplate: azureGatewayTemplate, Flags: append(serverFlags, cli.BoolFlag{ Name: "quiet", @@ -80,6 +73,59 @@ var gatewayCmd = cli.Command{ HideHelpCommand: true, } +const s3GatewayTemplate = `NAME: + {{.HelpName}} - {{.Usage}} + +USAGE: + {{.HelpName}} {{if .VisibleFlags}}[FLAGS]{{end}} [ENDPOINT] +{{if .VisibleFlags}} +FLAGS: + {{range .VisibleFlags}}{{.}} + {{end}}{{end}} +ENDPOINT: + S3 server endpoint. Default ENDPOINT is https://s3.amazonaws.com + +ENVIRONMENT VARIABLES: + ACCESS: + MINIO_ACCESS_KEY: Username or access key of S3 storage. + MINIO_SECRET_KEY: Password or secret key of S3 storage. + + BROWSER: + MINIO_BROWSER: To disable web browser access, set this value to "off". + +EXAMPLES: + 1. Start minio gateway server for AWS S3 backend. + $ export MINIO_ACCESS_KEY=accesskey + $ export MINIO_SECRET_KEY=secretkey + $ {{.HelpName}} + + 2. Start minio gateway server for S3 backend on custom endpoint. + $ export MINIO_ACCESS_KEY=Q3AM3UQ867SPQQA43P2F + $ export MINIO_SECRET_KEY=zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG + $ {{.HelpName}} https://play.minio.io:9000 +` + +var s3BackendCmd = cli.Command{ + Name: "s3", + Usage: "Amazon Simple Storage Service (S3).", + Action: s3GatewayMain, + CustomHelpTemplate: s3GatewayTemplate, + Flags: append(serverFlags, + cli.BoolFlag{ + Name: "quiet", + Usage: "Disable startup banner.", + }, + ), + HideHelpCommand: true, +} + +var gatewayCmd = cli.Command{ + Name: "gateway", + Usage: "Start object storage gateway.", + HideHelpCommand: true, + Subcommands: []cli.Command{azureBackendCmd, s3BackendCmd}, +} + // Represents the type of the gateway backend. type gatewayBackend string @@ -120,7 +166,7 @@ func mustSetBrowserSettingFromEnv() { // // - Azure Blob Storage. // - Add your favorite backend here. -func newGatewayLayer(backendType, endpoint, accessKey, secretKey string, secure bool) (GatewayLayer, error) { +func newGatewayLayer(backendType gatewayBackend, endpoint, accessKey, secretKey string, secure bool) (GatewayLayer, error) { switch gatewayBackend(backendType) { case azureBackend: @@ -212,12 +258,26 @@ func validateGatewayArguments(serverAddr, endpointAddr string) error { return nil } -// Handler for 'minio gateway'. -func gatewayMain(ctx *cli.Context) { - if !ctx.Args().Present() || ctx.Args().First() == "help" { - cli.ShowCommandHelpAndExit(ctx, "gateway", 1) +// Handler for 'minio gateway azure' command line. +func azureGatewayMain(ctx *cli.Context) { + if ctx.Args().Present() && ctx.Args().First() == "help" { + cli.ShowCommandHelpAndExit(ctx, "azure", 1) } + gatewayMain(ctx, azureBackend) +} + +// Handler for 'minio gateway s3' command line. +func s3GatewayMain(ctx *cli.Context) { + if ctx.Args().Present() && ctx.Args().First() == "help" { + cli.ShowCommandHelpAndExit(ctx, "s3", 1) + } + + gatewayMain(ctx, s3Backend) +} + +// Handler for 'minio gateway'. +func gatewayMain(ctx *cli.Context, backendType gatewayBackend) { // Fetch access and secret key from env. accessKey, secretKey := mustGetGatewayCredsFromEnv() @@ -234,13 +294,8 @@ func gatewayMain(ctx *cli.Context) { log.EnableQuiet() } - // First argument is selected backend type. - backendType := ctx.Args().Get(0) - // Second argument is the endpoint address (optional) - endpointAddr := ctx.Args().Get(1) - serverAddr := ctx.String("address") - + endpointAddr := ctx.Args().Get(0) err := validateGatewayArguments(serverAddr, endpointAddr) fatalIf(err, "Invalid argument") diff --git a/cmd/gateway-startup-msg.go b/cmd/gateway-startup-msg.go index 31641a02d..b89a753f3 100644 --- a/cmd/gateway-startup-msg.go +++ b/cmd/gateway-startup-msg.go @@ -23,7 +23,7 @@ import ( ) // Prints the formatted startup message. -func printGatewayStartupMessage(apiEndPoints []string, accessKey, secretKey, backendType string) { +func printGatewayStartupMessage(apiEndPoints []string, accessKey, secretKey string, backendType gatewayBackend) { // Prints credential. printGatewayCommonMsg(apiEndPoints, accessKey, secretKey) diff --git a/cmd/main.go b/cmd/main.go index c95dbf62a..8f871beb3 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -18,6 +18,7 @@ package cmd import ( "os" + "path/filepath" "sort" "github.com/minio/cli" @@ -59,7 +60,7 @@ VERSION: ` + Version + `{{ "\n"}}` -func newApp() *cli.App { +func newApp(name string) *cli.App { // Collection of minio commands currently supported are. commands := []cli.Command{} @@ -108,7 +109,7 @@ func newApp() *cli.App { } app := cli.NewApp() - app.Name = "Minio" + app.Name = name app.Author = "Minio.io" app.Version = Version app.Usage = "Cloud Storage Server." @@ -137,7 +138,8 @@ func newApp() *cli.App { // Main main for minio server. func Main(args []string) { - app := newApp() + name := filepath.Base(args[0]) + app := newApp(name) // Run the app - exit on error. if err := app.Run(args); err != nil { From 48dbd4998078032af19432a775a688feb67b8ed7 Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Fri, 9 Jun 2017 02:42:12 -0700 Subject: [PATCH 71/80] Add support for kubernetes host detection (#4514) Additionally improve what we print for `docker pull` such that its precisely the relevant release tag. Fixes #4456 --- cmd/update-main.go | 40 ++++++++++++++++++++++++++++++++++------ cmd/update-main_test.go | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 6 deletions(-) diff --git a/cmd/update-main.go b/cmd/update-main.go index a8d902091..2639ef9cb 100644 --- a/cmd/update-main.go +++ b/cmd/update-main.go @@ -107,7 +107,8 @@ func isDocker(cgroupFile string) (bool, error) { return bytes.Contains(cgroup, []byte("docker")), err } -// IsDocker - returns if the environment is docker or not. +// IsDocker - returns if the environment minio is running +// is docker or not. func IsDocker() bool { found, err := isDocker("/proc/self/cgroup") fatalIf(err, "Error in docker check.") @@ -115,6 +116,16 @@ func IsDocker() bool { return found } +// IsKubernetes returns if the environment minio is +// running is kubernetes or not. +func IsKubernetes() bool { + // Kubernetes env used to validate if we are + // indeed running inside a kubernetes pod + // is KUBERNETES_SERVICE_HOST but in future + // we might need to enhance this. + return os.Getenv("KUBERNETES_SERVICE_HOST") != "" +} + func isSourceBuild(minioVersion string) bool { _, err := time.Parse(time.RFC3339, minioVersion) return err != nil @@ -127,7 +138,8 @@ func IsSourceBuild() bool { // DO NOT CHANGE USER AGENT STYLE. // The style should be -// Minio (; [; docker][; source]) Minio/ Minio/ Minio/ +// +// Minio (; [; kubernetes][; 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(mode string) string { @@ -135,6 +147,9 @@ func getUserAgent(mode string) string { if mode != "" { userAgent += "; " + mode } + if IsKubernetes() { + userAgent += "; kubernetes" + } if IsDocker() { userAgent += "; docker" } @@ -226,11 +241,24 @@ func getLatestReleaseTime(timeout time.Duration, mode string) (releaseTime time. return parseReleaseData(data) } -func getDownloadURL() (downloadURL string) { - if IsDocker() { - return "docker pull minio/minio" +// Kubernetes deploy doc link. +const kubernetesDeploymentDoc = "https://docs.minio.io/docs/deploy-minio-on-kubernetes" + +func getDownloadURL(buildDate time.Time) (downloadURL string) { + // Check if we are in kubernetes environment, return + // deployment guide for update procedures. + if IsKubernetes() { + return kubernetesDeploymentDoc } + // Check if we are docker environment, return docker update command + if IsDocker() { + // Construct release tag name. + rTag := "RELEASE." + buildDate.Format(minioReleaseTagTimeLayout) + return fmt.Sprintf("docker pull minio/minio:%s", rTag) + } + + // For binary only installations, then we just show binary download link. if runtime.GOOS == "windows" { return minioReleaseURL + "minio.exe" } @@ -254,7 +282,7 @@ func getUpdateInfo(timeout time.Duration, mode string) (older time.Duration, if latestReleaseTime.After(currentReleaseTime) { older = latestReleaseTime.Sub(currentReleaseTime) - downloadURL = getDownloadURL() + downloadURL = getDownloadURL(latestReleaseTime) } return older, downloadURL, nil diff --git a/cmd/update-main_test.go b/cmd/update-main_test.go index ee1ce24a1..0946b34aa 100644 --- a/cmd/update-main_test.go +++ b/cmd/update-main_test.go @@ -30,6 +30,28 @@ import ( "time" ) +func TestDownloadURL(t *testing.T) { + minioVersion1 := UTCNow() + durl := getDownloadURL(minioVersion1) + if runtime.GOOS == "windows" { + if durl != minioReleaseURL+"minio.exe" { + t.Errorf("Expected %s, got %s", minioReleaseURL+"minio.exe", durl) + } + } else { + if durl != minioReleaseURL+"minio" { + t.Errorf("Expected %s, got %s", minioReleaseURL+"minio", durl) + } + } + + os.Setenv("KUBERNETES_SERVICE_HOST", "10.11.148.5") + defer os.Unsetenv("KUBERNETES_SERVICE_HOST") + + durl = getDownloadURL(minioVersion1) + if durl != kubernetesDeploymentDoc { + t.Errorf("Expected %s, got %s", kubernetesDeploymentDoc, durl) + } +} + func TestGetCurrentReleaseTime(t *testing.T) { minioVersion1 := UTCNow().Format(time.RFC3339) releaseTime1, _ := time.Parse(time.RFC3339, minioVersion1) @@ -136,6 +158,21 @@ func TestGetCurrentReleaseTime(t *testing.T) { } } +// Tests if the environment we are running is in kubernetes. +func TestIsKubernetes(t *testing.T) { + os.Setenv("KUBERNETES_SERVICE_HOST", "10.11.148.5") + kubernetes := IsKubernetes() + if !kubernetes { + t.Fatalf("Expected %t, got %t", true, kubernetes) + } + os.Unsetenv("KUBERNETES_SERVICE_HOST") + kubernetes = IsKubernetes() + if kubernetes { + t.Fatalf("Expected %t, got %t", false, kubernetes) + } +} + +// Tests if the environment we are running is in docker. func TestIsDocker(t *testing.T) { createTempFile := func(content string) string { tmpfile, err := ioutil.TempFile("", "isdocker-testcase") @@ -164,6 +201,7 @@ func TestIsDocker(t *testing.T) { 1:name=systemd:/user.slice/user-1000.slice/user@1000.service/gnome-terminal-server.service `) defer os.Remove(filename1) + filename2 := createTempFile(`14:name=systemd:/docker/d5eb950884d828237f60f624ff575a1a7a4daa28a8d4d750040527ed9545e727 13:pids:/docker/d5eb950884d828237f60f624ff575a1a7a4daa28a8d4d750040527ed9545e727 12:hugetlb:/docker/d5eb950884d828237f60f624ff575a1a7a4daa28a8d4d750040527ed9545e727 From b28d5fa63372e07b92ca1c3560f612331f00b8ce Mon Sep 17 00:00:00 2001 From: Dee Koder Date: Fri, 9 Jun 2017 21:24:32 +0000 Subject: [PATCH 72/80] Clarify macOS instructions on brew paths. Deleted homebrew upgrade instructions. (#4501) --- README.md | 15 ++++++++++++++- docs/minio_homebrew.md | 35 ----------------------------------- 2 files changed, 14 insertions(+), 36 deletions(-) delete mode 100644 docs/minio_homebrew.md diff --git a/README.md b/README.md index 842214db4..6c5c09006 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,20 @@ Install minio packages using [Homebrew](http://brew.sh/) brew install minio/stable/minio minio server ~/Photos ``` -Note: If you are upgrading minio on macOS, please see instructions [here](https://github.com/minio/minio/blob/master/docs/minio_homebrew.md). +#### Note +If you previously installed minio using `brew install minio` then uninstall minio as shown below + +``` +brew uninstall minio +``` + +Then re-install the latest minio using: + +``` +brew install minio/stable/minio +``` + +>`brew install minio` and `brew upgrade minio` will no longer install/upgrade the latest minio binaries on macOS. Upstream bugs in golang 1.8 broke Minio brew installer. Use the updated `minio/stable/minio` in your brew paths. ### Binary Download | Platform| Architecture | URL| diff --git a/docs/minio_homebrew.md b/docs/minio_homebrew.md deleted file mode 100644 index 3d3b51c72..000000000 --- a/docs/minio_homebrew.md +++ /dev/null @@ -1,35 +0,0 @@ -# Minio Installation on macOS - -## Fresh Install - -Install Minio on macOS via brew. - -``` -brew install minio/stable/minio -minio server ~/Photos -``` - -## Upgrade - -Step 1: Uninstall minio if you installed it using `brew install minio` - -``` -brew uninstall minio -``` -Step 2: Fresh Install using new path - -Once you remove minio completely from your system, proceed to do : - -``` -brew install minio/stable/minio -``` - -## Important Breaking Change - -#### Installation Path Changes for minio in brew - -> `brew upgrade minio` and `brew install minio` commands will no longer install the latest minio binaries on macOS. Use `brew install minio/stable/minio` in all your brew paths. - -Upstream bugs in golang 1.8 broke Minio brew installer. We will re-enable minio installation on macOS via `brew install minio` at a later date. - - From f59d7a04b4ab32daf13010136b1046b5b8694ee7 Mon Sep 17 00:00:00 2001 From: Nitish Tiwari Date: Sat, 10 Jun 2017 21:44:20 -0700 Subject: [PATCH 73/80] Removed references to docker-machine in Swarm guide (#4502) --- docs/orchestration/docker-swarm/README.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/docs/orchestration/docker-swarm/README.md b/docs/orchestration/docker-swarm/README.md index 142a3bdef..2e226b2f3 100644 --- a/docs/orchestration/docker-swarm/README.md +++ b/docs/orchestration/docker-swarm/README.md @@ -10,19 +10,20 @@ As of [Docker Engine v1.13.0](https://blog.docker.com/2017/01/whats-new-in-docke * 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 +Create a swarm on the manager node by running ```shell docker swarm init --advertise-addr ``` +Once the swarm is initialized, you'll see the below response. -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/). +```shell +docker swarm join \ + --token SWMTKN-1-49nj1cmql0jkz5s954yi3oex3nedyz0fb0xx14ie39trti4wxv-8vxv8rssmk743ojnwacrr2e7c \ + 192.168.99.100:2377 +``` + +You can now [add worker nodes](https://docs.docker.com/engine/swarm/swarm-tutorial/add-nodes/) to the swarm by running the above command. Find detailed steps to create the swarm on [Docker documentation site](https://docs.docker.com/engine/swarm/swarm-tutorial/create-swarm/). ## 3. Create Docker secrets for Minio From 6f4862659f7f86e1637fe579343bc878f499d9bf Mon Sep 17 00:00:00 2001 From: Frank Wessels Date: Mon, 12 Jun 2017 17:20:29 -0700 Subject: [PATCH 74/80] Investigate issue #4461 (#4521) * Code to investigate issue #4461 (rare test failure in TestListenAndServeTLS) * Use UTCNow() instead of time.Now().UTC() --- cmd/server-mux_test.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cmd/server-mux_test.go b/cmd/server-mux_test.go index 614621b2f..4a4007710 100644 --- a/cmd/server-mux_test.go +++ b/cmd/server-mux_test.go @@ -390,11 +390,17 @@ func TestServerListenAndServeTLS(t *testing.T) { Transport: tr, } // Keep trying the server until it's accepting connections + start := UTCNow() for { res, _ := client.Get("https://" + addr) if res != nil && res.StatusCode == http.StatusOK { break } + // Explicit check to terminate loop after 5 minutes + // (for investigational purpose of issue #4461) + if UTCNow().Sub(start) >= 5*time.Minute { + t.Fatalf("Failed to establish connection after 5 minutes") + } } // Once a request succeeds, subsequent requests should From b8463a738cfae55cf7904d503c7dde525d404b94 Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Mon, 12 Jun 2017 17:33:21 -0700 Subject: [PATCH 75/80] Add support for DCOS host detection, improve Docker detection. (#4525) isDocker was currently reading from `/proc/cgroup` file. But this file alone is rather not conclusive evidence. Docker internally has `.dockerenv` as a special file which we should use instead. Fixes #4456 --- cmd/main.go | 6 +-- cmd/update-main.go | 65 +++++++++++++++++++------ cmd/update-main_test.go | 104 ++++++++++++++++++++++++++-------------- 3 files changed, 122 insertions(+), 53 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index 8f871beb3..f7e931e87 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -138,11 +138,11 @@ func newApp(name string) *cli.App { // Main main for minio server. func Main(args []string) { - name := filepath.Base(args[0]) - app := newApp(name) + // Set the minio app name. + appName := filepath.Base(args[0]) // Run the app - exit on error. - if err := app.Run(args); err != nil { + if err := newApp(appName).Run(args); err != nil { os.Exit(1) } } diff --git a/cmd/update-main.go b/cmd/update-main.go index 2639ef9cb..a0e7953ba 100644 --- a/cmd/update-main.go +++ b/cmd/update-main.go @@ -17,7 +17,6 @@ package cmd import ( - "bytes" "fmt" "io/ioutil" "net/http" @@ -98,26 +97,41 @@ func GetCurrentReleaseTime() (releaseTime time.Time, err error) { return getCurrentReleaseTime(Version, os.Args[0]) } -func isDocker(cgroupFile string) (bool, error) { - cgroup, err := ioutil.ReadFile(cgroupFile) - if os.IsNotExist(err) { - err = nil +// Check if we are indeed inside docker. +// https://github.com/moby/moby/blob/master/daemon/initlayer/setup_unix.go#L25 +// +// "/.dockerenv": "file", +// +func isDocker(dockerEnvFile string) (ok bool, err error) { + _, err = os.Stat(dockerEnvFile) + if err != nil { + if os.IsNotExist(err) { + err = nil + } + return false, err } - - return bytes.Contains(cgroup, []byte("docker")), err + return true, nil } // IsDocker - returns if the environment minio is running // is docker or not. func IsDocker() bool { - found, err := isDocker("/proc/self/cgroup") - fatalIf(err, "Error in docker check.") + found, err := isDocker("/.dockerenv") + // We don't need to fail for this check, log + // an error and return false. + errorIf(err, "Error in docker check.") return found } -// IsKubernetes returns if the environment minio is -// running is kubernetes or not. +// IsDCOS returns true if minio is running in DCOS. +func IsDCOS() bool { + // http://mesos.apache.org/documentation/latest/docker-containerizer/ + // Mesos docker containerizer sets this value + return os.Getenv("MESOS_CONTAINER_NAME") != "" +} + +// IsKubernetes returns true if minio is running in kubernetes. func IsKubernetes() bool { // Kubernetes env used to validate if we are // indeed running inside a kubernetes pod @@ -139,7 +153,7 @@ func IsSourceBuild() bool { // DO NOT CHANGE USER AGENT STYLE. // The style should be // -// Minio (; [; kubernetes][; docker][; source]) Minio/ Minio/ Minio/ +// Minio (; [; dcos][; kubernetes][; docker][; source]) Minio/ Minio/ Minio/ [Minio/univers-] // // For any change here should be discussed by openning an issue at https://github.com/minio/minio/issues. func getUserAgent(mode string) string { @@ -147,6 +161,9 @@ func getUserAgent(mode string) string { if mode != "" { userAgent += "; " + mode } + if IsDCOS() { + userAgent += "; dcos" + } if IsKubernetes() { userAgent += "; kubernetes" } @@ -156,8 +173,15 @@ func getUserAgent(mode string) string { if IsSourceBuild() { userAgent += "; source" } - userAgent += ") " + " Minio/" + Version + " Minio/" + ReleaseTag + " Minio/" + CommitID + userAgent += ") Minio/" + Version + " Minio/" + ReleaseTag + " Minio/" + CommitID + if IsDCOS() { + universePkgVersion := os.Getenv("MARATHON_APP_LABEL_DCOS_PACKAGE_VERSION") + // On DC/OS environment try to the get universe package version. + if universePkgVersion != "" { + userAgent += " Minio/" + "universe-" + universePkgVersion + } + } return userAgent } @@ -241,10 +265,21 @@ func getLatestReleaseTime(timeout time.Duration, mode string) (releaseTime time. return parseReleaseData(data) } -// Kubernetes deploy doc link. -const kubernetesDeploymentDoc = "https://docs.minio.io/docs/deploy-minio-on-kubernetes" +const ( + // Kubernetes deployment doc link. + kubernetesDeploymentDoc = "https://docs.minio.io/docs/deploy-minio-on-kubernetes" + + // Mesos deployment doc link. + mesosDeploymentDoc = "https://docs.minio.io/docs/deploy-minio-on-dc-os" +) func getDownloadURL(buildDate time.Time) (downloadURL string) { + // Check if we are in DCOS environment, return + // deployment guide for update procedures. + if IsDCOS() { + return mesosDeploymentDoc + } + // Check if we are in kubernetes environment, return // deployment guide for update procedures. if IsKubernetes() { diff --git a/cmd/update-main_test.go b/cmd/update-main_test.go index 0946b34aa..175f97f2b 100644 --- a/cmd/update-main_test.go +++ b/cmd/update-main_test.go @@ -44,12 +44,18 @@ func TestDownloadURL(t *testing.T) { } os.Setenv("KUBERNETES_SERVICE_HOST", "10.11.148.5") - defer os.Unsetenv("KUBERNETES_SERVICE_HOST") - durl = getDownloadURL(minioVersion1) if durl != kubernetesDeploymentDoc { t.Errorf("Expected %s, got %s", kubernetesDeploymentDoc, durl) } + os.Unsetenv("KUBERNETES_SERVICE_HOST") + + os.Setenv("MESOS_CONTAINER_NAME", "mesos-1111") + durl = getDownloadURL(minioVersion1) + if durl != mesosDeploymentDoc { + t.Errorf("Expected %s, got %s", mesosDeploymentDoc, durl) + } + os.Unsetenv("MESOS_CONTAINER_NAME") } func TestGetCurrentReleaseTime(t *testing.T) { @@ -158,6 +164,63 @@ func TestGetCurrentReleaseTime(t *testing.T) { } } +// Tests user agent string. +func TestUserAgent(t *testing.T) { + testCases := []struct { + envName string + envValue string + mode string + expectedStr string + }{ + { + envName: "", + envValue: "", + mode: globalMinioModeFS, + expectedStr: fmt.Sprintf("Minio (%s; %s; %s; source) Minio/DEVELOPMENT.GOGET Minio/DEVELOPMENT.GOGET Minio/DEVELOPMENT.GOGET", runtime.GOOS, runtime.GOARCH, globalMinioModeFS), + }, + { + envName: "MESOS_CONTAINER_NAME", + envValue: "mesos-11111", + mode: globalMinioModeXL, + expectedStr: fmt.Sprintf("Minio (%s; %s; %s; %s; source) Minio/DEVELOPMENT.GOGET Minio/DEVELOPMENT.GOGET Minio/DEVELOPMENT.GOGET Minio/universe-%s", runtime.GOOS, runtime.GOARCH, globalMinioModeXL, "dcos", "mesos-1111"), + }, + { + envName: "KUBERNETES_SERVICE_HOST", + envValue: "10.11.148.5", + mode: globalMinioModeXL, + expectedStr: fmt.Sprintf("Minio (%s; %s; %s; %s; source) Minio/DEVELOPMENT.GOGET Minio/DEVELOPMENT.GOGET Minio/DEVELOPMENT.GOGET", runtime.GOOS, runtime.GOARCH, globalMinioModeXL, "kubernetes"), + }, + } + + for i, testCase := range testCases { + os.Setenv(testCase.envName, testCase.envValue) + if testCase.envName == "MESOS_CONTAINER_NAME" { + os.Setenv("MARATHON_APP_LABEL_DCOS_PACKAGE_VERSION", "mesos-1111") + } + str := getUserAgent(testCase.mode) + if str != testCase.expectedStr { + t.Errorf("Test %d: expected: %s, got: %s", i+1, testCase.expectedStr, str) + } + os.Unsetenv("MARATHON_APP_LABEL_DCOS_PACKAGE_VERSION") + os.Unsetenv(testCase.envName) + } +} + +// Tests if the environment we are running is in DCOS. +func TestIsDCOS(t *testing.T) { + os.Setenv("MESOS_CONTAINER_NAME", "mesos-1111") + dcos := IsDCOS() + if !dcos { + t.Fatalf("Expected %t, got %t", true, dcos) + } + + os.Unsetenv("MESOS_CONTAINER_NAME") + dcos = IsDCOS() + if dcos { + t.Fatalf("Expected %t, got %t", false, dcos) + } +} + // Tests if the environment we are running is in kubernetes. func TestIsKubernetes(t *testing.T) { os.Setenv("KUBERNETES_SERVICE_HOST", "10.11.148.5") @@ -188,36 +251,8 @@ func TestIsDocker(t *testing.T) { return tmpfile.Name() } - filename1 := createTempFile(`11:pids:/user.slice/user-1000.slice/user@1000.service -10:blkio:/ -9:hugetlb:/ -8:perf_event:/ -7:cpuset:/ -6:devices:/user.slice -5:net_cls,net_prio:/ -4:cpu,cpuacct:/ -3:memory:/user/bala/0 -2:freezer:/user/bala/0 -1:name=systemd:/user.slice/user-1000.slice/user@1000.service/gnome-terminal-server.service -`) - defer os.Remove(filename1) - - filename2 := createTempFile(`14:name=systemd:/docker/d5eb950884d828237f60f624ff575a1a7a4daa28a8d4d750040527ed9545e727 -13:pids:/docker/d5eb950884d828237f60f624ff575a1a7a4daa28a8d4d750040527ed9545e727 -12:hugetlb:/docker/d5eb950884d828237f60f624ff575a1a7a4daa28a8d4d750040527ed9545e727 -11:net_prio:/docker/d5eb950884d828237f60f624ff575a1a7a4daa28a8d4d750040527ed9545e727 -10:perf_event:/docker/d5eb950884d828237f60f624ff575a1a7a4daa28a8d4d750040527ed9545e727 -9:net_cls:/docker/d5eb950884d828237f60f624ff575a1a7a4daa28a8d4d750040527ed9545e727 -8:freezer:/docker/d5eb950884d828237f60f624ff575a1a7a4daa28a8d4d750040527ed9545e727 -7:devices:/docker/d5eb950884d828237f60f624ff575a1a7a4daa28a8d4d750040527ed9545e727 -6:memory:/docker/d5eb950884d828237f60f624ff575a1a7a4daa28a8d4d750040527ed9545e727 -5:blkio:/docker/d5eb950884d828237f60f624ff575a1a7a4daa28a8d4d750040527ed9545e727 -4:cpuacct:/docker/d5eb950884d828237f60f624ff575a1a7a4daa28a8d4d750040527ed9545e727 -3:cpu:/docker/d5eb950884d828237f60f624ff575a1a7a4daa28a8d4d750040527ed9545e727 -2:cpuset:/docker/d5eb950884d828237f60f624ff575a1a7a4daa28a8d4d750040527ed9545e727 -1:name=openrc:/docker -`) - defer os.Remove(filename2) + filename := createTempFile("") + defer os.Remove(filename) testCases := []struct { filename string @@ -226,8 +261,7 @@ func TestIsDocker(t *testing.T) { }{ {"", false, nil}, {"/tmp/non-existing-file", false, nil}, - {filename1, false, nil}, - {filename2, true, nil}, + {filename, true, nil}, } if runtime.GOOS == "linux" { @@ -235,7 +269,7 @@ func TestIsDocker(t *testing.T) { filename string expectedResult bool expectedErr error - }{"/proc/1/cwd", false, fmt.Errorf("open /proc/1/cwd: permission denied")}) + }{"/proc/1/cwd", false, errors.New("stat /proc/1/cwd: permission denied")}) } for _, testCase := range testCases { From 075b8903d774e04464918654ef5c2fdaf7825747 Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Mon, 12 Jun 2017 17:40:28 -0700 Subject: [PATCH 76/80] fs: Add safe locking semantics for `format.json` (#4523) This patch also reverts previous changes which were merged for migration to the newer disk format. We will be bringing these changes in subsequent releases. But we wish to add protection in this release such that future release migrations are protected. Revert "fs: Migration should handle bucketConfigs as regular objects. (#4482)" This reverts commit 976870a39100ecc10d12ef163deedc1575d66b67. Revert "fs: Migrate object metadata to objects directory. (#4195)" This reverts commit 76f4f20609ae1c92a5e6bb75fc4a5e8ddac9e9ff. --- cmd/format-config-v1.go | 105 ++++++- cmd/format-config-v1_test.go | 244 +++++++++++++--- cmd/fs-v1-helpers.go | 19 +- cmd/fs-v1-metadata.go | 245 ++++++---------- cmd/fs-v1-metadata_test.go | 4 +- cmd/fs-v1-multipart.go | 2 +- cmd/fs-v1.go | 157 +---------- cmd/fs-v1_test.go | 514 ---------------------------------- cmd/object-api-common.go | 3 - cmd/posix-list-dir-others.go | 2 - cmd/posix.go | 2 +- cmd/web-handlers_test.go | 2 +- cmd/xl-v1-healing.go | 2 +- cmd/xl-v1-healing_test.go | 10 +- cmd/xl-v1.go | 6 - docs/shared-backend/DESIGN.md | 4 +- pkg/lock/lock.go | 6 + pkg/lock/lock_nix.go | 35 ++- pkg/lock/lock_solaris.go | 45 ++- pkg/lock/lock_windows.go | 35 ++- 20 files changed, 509 insertions(+), 933 deletions(-) diff --git a/cmd/format-config-v1.go b/cmd/format-config-v1.go index 4ea2bb24e..cda9896a5 100644 --- a/cmd/format-config-v1.go +++ b/cmd/format-config-v1.go @@ -33,6 +33,15 @@ type fsFormat struct { Version string `json:"version"` } +// FS format version strings. +const ( + // Represents the current backend disk structure + // version under `.minio.sys` and actual data namespace. + + // formatConfigV1.fsFormat.Version + fsFormatBackendV1 = "1" +) + // xlFormat - structure holding 'xl' format. type xlFormat struct { Version string `json:"version"` // Version of 'xl' format. @@ -42,6 +51,15 @@ type xlFormat struct { JBOD []string `json:"jbod"` } +// XL format version strings. +const ( + // Represents the current backend disk structure + // version under `.minio.sys` and actual data namespace. + + // formatConfigV1.xlFormat.Version + xlFormatBackendV1 = "1" +) + // formatConfigV1 - structure holds format config version '1'. type formatConfigV1 struct { Version string `json:"version"` // Version of the format config. @@ -51,6 +69,68 @@ type formatConfigV1 struct { XL *xlFormat `json:"xl,omitempty"` // XL field holds xl format. } +// Format json file. +const ( + // Format config file carries backend format specific details. + formatConfigFile = "format.json" + + // Format config tmp file carries backend format. + formatConfigFileTmp = "format.json.tmp" +) + +// `format.json` version value. +const ( + // formatConfigV1.Version represents the version string + // of the current structure and its fields in `format.json`. + formatFileV1 = "1" + + // Future `format.json` structure changes should have + // its own version and should be subsequently listed here. +) + +// Constitutes `format.json` backend name. +const ( + // Represents FS backend. + formatBackendFS = "fs" + + // Represents XL backend. + formatBackendXL = "xl" +) + +// CheckFS if the format is FS and is valid with right values +// returns appropriate errors otherwise. +func (f *formatConfigV1) CheckFS() error { + // Validate if format config version is v1. + if f.Version != formatFileV1 { + return fmt.Errorf("Unknown format file version '%s'", f.Version) + } + + // Validate if we have the expected format. + if f.Format != formatBackendFS { + return fmt.Errorf("FS backend format required. Found '%s'", f.Format) + } + + // Check if format is currently supported. + if f.FS.Version != fsFormatBackendV1 { + return fmt.Errorf("Unknown backend FS format version '%s'", f.FS.Version) + } + + // Success. + return nil +} + +// LoadFormat - loads format config v1, returns `errUnformattedDisk` +// if reading format.json fails with io.EOF. +func (f *formatConfigV1) LoadFormat(lk *lock.LockedFile) error { + _, err := f.ReadFrom(lk) + if errorCause(err) == io.EOF { + // No data on disk `format.json` still empty + // treat it as unformatted disk. + return traceError(errUnformattedDisk) + } + return err +} + func (f *formatConfigV1) WriteTo(lk *lock.LockedFile) (n int64, err error) { // Serialize to prepare to write to disk. var fbytes []byte @@ -88,6 +168,21 @@ func (f *formatConfigV1) ReadFrom(lk *lock.LockedFile) (n int64, err error) { return int64(len(fbytes)), nil } +func newFSFormat() (format *formatConfigV1) { + return newFSFormatV1() +} + +// newFSFormatV1 - initializes new formatConfigV1 with FS format info. +func newFSFormatV1() (format *formatConfigV1) { + return &formatConfigV1{ + Version: formatFileV1, + Format: formatBackendFS, + FS: &fsFormat{ + Version: fsFormatBackendV1, + }, + } +} + /* All disks online @@ -811,10 +906,10 @@ func loadFormatXL(bootstrapDisks []StorageAPI, readQuorum int) (disks []StorageA func checkFormatXLValue(formatXL *formatConfigV1) error { // Validate format version and format type. - if formatXL.Version != "1" { + if formatXL.Version != formatFileV1 { return fmt.Errorf("Unsupported version of backend format [%s] found", formatXL.Version) } - if formatXL.Format != "xl" { + if formatXL.Format != formatBackendXL { return fmt.Errorf("Unsupported backend format [%s] found", formatXL.Format) } if formatXL.XL.Version != "1" { @@ -916,10 +1011,10 @@ func initFormatXL(storageDisks []StorageAPI) (err error) { } // Allocate format config. formats[index] = &formatConfigV1{ - Version: "1", - Format: "xl", + Version: formatFileV1, + Format: formatBackendXL, XL: &xlFormat{ - Version: "1", + Version: xlFormatBackendV1, Disk: mustGetUUID(), }, } diff --git a/cmd/format-config-v1_test.go b/cmd/format-config-v1_test.go index 6e00adda6..9e85f8083 100644 --- a/cmd/format-config-v1_test.go +++ b/cmd/format-config-v1_test.go @@ -18,7 +18,12 @@ package cmd import ( "bytes" + "errors" + "os" + "path/filepath" "testing" + + "github.com/minio/minio/pkg/lock" ) // generates a valid format.json for XL backend. @@ -30,10 +35,10 @@ func genFormatXLValid() []*formatConfigV1 { } for index := range jbod { formatConfigs[index] = &formatConfigV1{ - Version: "1", - Format: "xl", + Version: formatFileV1, + Format: formatBackendXL, XL: &xlFormat{ - Version: "1", + Version: xlFormatBackendV1, Disk: jbod[index], JBOD: jbod, }, @@ -51,10 +56,10 @@ func genFormatXLInvalidVersion() []*formatConfigV1 { } for index := range jbod { formatConfigs[index] = &formatConfigV1{ - Version: "1", - Format: "xl", + Version: formatFileV1, + Format: formatBackendXL, XL: &xlFormat{ - Version: "1", + Version: xlFormatBackendV1, Disk: jbod[index], JBOD: jbod, }, @@ -75,10 +80,10 @@ func genFormatXLInvalidFormat() []*formatConfigV1 { } for index := range jbod { formatConfigs[index] = &formatConfigV1{ - Version: "1", - Format: "xl", + Version: formatFileV1, + Format: formatBackendXL, XL: &xlFormat{ - Version: "1", + Version: xlFormatBackendV1, Disk: jbod[index], JBOD: jbod, }, @@ -99,10 +104,10 @@ func genFormatXLInvalidXLVersion() []*formatConfigV1 { } for index := range jbod { formatConfigs[index] = &formatConfigV1{ - Version: "1", - Format: "xl", + Version: formatFileV1, + Format: formatBackendXL, XL: &xlFormat{ - Version: "1", + Version: xlFormatBackendV1, Disk: jbod[index], JBOD: jbod, }, @@ -116,8 +121,8 @@ func genFormatXLInvalidXLVersion() []*formatConfigV1 { func genFormatFS() *formatConfigV1 { return &formatConfigV1{ - Version: "1", - Format: "fs", + Version: formatFileV1, + Format: formatBackendFS, } } @@ -130,10 +135,10 @@ func genFormatXLInvalidJBODCount() []*formatConfigV1 { } for index := range jbod { formatConfigs[index] = &formatConfigV1{ - Version: "1", - Format: "xl", + Version: formatFileV1, + Format: formatBackendXL, XL: &xlFormat{ - Version: "1", + Version: xlFormatBackendV1, Disk: jbod[index], JBOD: jbod, }, @@ -151,10 +156,10 @@ func genFormatXLInvalidJBOD() []*formatConfigV1 { } for index := range jbod { formatConfigs[index] = &formatConfigV1{ - Version: "1", - Format: "xl", + Version: formatFileV1, + Format: formatBackendXL, XL: &xlFormat{ - Version: "1", + Version: xlFormatBackendV1, Disk: jbod[index], JBOD: jbod, }, @@ -178,10 +183,10 @@ func genFormatXLInvalidDisks() []*formatConfigV1 { } for index := range jbod { formatConfigs[index] = &formatConfigV1{ - Version: "1", - Format: "xl", + Version: formatFileV1, + Format: formatBackendXL, XL: &xlFormat{ - Version: "1", + Version: xlFormatBackendV1, Disk: jbod[index], JBOD: jbod, }, @@ -202,10 +207,10 @@ func genFormatXLInvalidDisksOrder() []*formatConfigV1 { } for index := range jbod { formatConfigs[index] = &formatConfigV1{ - Version: "1", - Format: "xl", + Version: formatFileV1, + Format: formatBackendXL, XL: &xlFormat{ - Version: "1", + Version: xlFormatBackendV1, Disk: jbod[index], JBOD: jbod, }, @@ -240,10 +245,10 @@ func prepareFormatXLHealFreshDisks(obj ObjectLayer) ([]StorageAPI, error) { // Remove the content of export dir 10 but preserve .minio.sys because it is automatically // created when minio starts for i := 3; i <= 5; i++ { - if err = xl.storageDisks[i].DeleteFile(".minio.sys", "format.json"); err != nil { + if err = xl.storageDisks[i].DeleteFile(minioMetaBucket, formatConfigFile); err != nil { return []StorageAPI{}, err } - if err = xl.storageDisks[i].DeleteFile(".minio.sys", "tmp"); err != nil { + if err = xl.storageDisks[i].DeleteFile(minioMetaBucket, "tmp"); err != nil { return []StorageAPI{}, err } if err = xl.storageDisks[i].DeleteFile(bucket, object+"/xl.json"); err != nil { @@ -361,19 +366,19 @@ func TestFormatXLHealCorruptedDisks(t *testing.T) { } // Now, remove two format files.. Load them and reorder - if err = xl.storageDisks[3].DeleteFile(".minio.sys", "format.json"); err != nil { + if err = xl.storageDisks[3].DeleteFile(minioMetaBucket, formatConfigFile); err != nil { t.Fatal(err) } - if err = xl.storageDisks[11].DeleteFile(".minio.sys", "format.json"); err != nil { + if err = xl.storageDisks[11].DeleteFile(minioMetaBucket, formatConfigFile); err != nil { t.Fatal(err) } // Remove the content of export dir 10 but preserve .minio.sys because it is automatically // created when minio starts - if err = xl.storageDisks[10].DeleteFile(".minio.sys", "format.json"); err != nil { + if err = xl.storageDisks[10].DeleteFile(minioMetaBucket, formatConfigFile); err != nil { t.Fatal(err) } - if err = xl.storageDisks[10].DeleteFile(".minio.sys", "tmp"); err != nil { + if err = xl.storageDisks[10].DeleteFile(minioMetaBucket, "tmp"); err != nil { t.Fatal(err) } if err = xl.storageDisks[10].DeleteFile(bucket, object+"/xl.json"); err != nil { @@ -434,10 +439,10 @@ func TestFormatXLReorderByInspection(t *testing.T) { } // Now, remove two format files.. Load them and reorder - if err = xl.storageDisks[3].DeleteFile(".minio.sys", "format.json"); err != nil { + if err = xl.storageDisks[3].DeleteFile(minioMetaBucket, formatConfigFile); err != nil { t.Fatal(err) } - if err = xl.storageDisks[5].DeleteFile(".minio.sys", "format.json"); err != nil { + if err = xl.storageDisks[5].DeleteFile(minioMetaBucket, formatConfigFile); err != nil { t.Fatal(err) } @@ -555,10 +560,10 @@ func TestSavedUUIDOrder(t *testing.T) { } for index := range jbod { formatConfigs[index] = &formatConfigV1{ - Version: "1", - Format: "xl", + Version: formatFileV1, + Format: formatBackendXL, XL: &xlFormat{ - Version: "1", + Version: xlFormatBackendV1, Disk: jbod[index], JBOD: jbod, }, @@ -682,6 +687,163 @@ func TestGenericFormatCheckXL(t *testing.T) { } } +// TestFSCheckFormatFSErr - test loadFormatFS loading older format. +func TestFSCheckFormatFSErr(t *testing.T) { + // Prepare for testing + disk := filepath.Join(globalTestTmpDir, "minio-"+nextSuffix()) + defer removeAll(disk) + + // Assign a new UUID. + uuid := mustGetUUID() + + // Initialize meta volume, if volume already exists ignores it. + if err := initMetaVolumeFS(disk, uuid); err != nil { + t.Fatal(err) + } + + testCases := []struct { + format *formatConfigV1 + formatWriteErr error + formatCheckErr error + shouldPass bool + }{ + { + format: &formatConfigV1{ + Version: formatFileV1, + Format: formatBackendFS, + FS: &fsFormat{ + Version: fsFormatBackendV1, + }, + }, + formatCheckErr: nil, + shouldPass: true, + }, + { + format: &formatConfigV1{ + Version: formatFileV1, + Format: formatBackendFS, + FS: &fsFormat{ + Version: "10", + }, + }, + formatCheckErr: errors.New("Unknown backend FS format version '10'"), + shouldPass: false, + }, + { + format: &formatConfigV1{ + Version: formatFileV1, + Format: "garbage", + FS: &fsFormat{ + Version: fsFormatBackendV1, + }, + }, + formatCheckErr: errors.New("FS backend format required. Found 'garbage'"), + }, + { + format: &formatConfigV1{ + Version: "-1", + Format: formatBackendFS, + FS: &fsFormat{ + Version: fsFormatBackendV1, + }, + }, + formatCheckErr: errors.New("Unknown format file version '-1'"), + }, + } + + fsFormatPath := pathJoin(disk, minioMetaBucket, formatConfigFile) + for i, testCase := range testCases { + lk, err := lock.LockedOpenFile(preparePath(fsFormatPath), os.O_RDWR|os.O_CREATE, 0600) + if err != nil { + t.Fatal(err) + } + _, err = testCase.format.WriteTo(lk) + lk.Close() + if err != nil { + t.Fatalf("Test %d: Expected nil, got %s", i+1, err) + } + + lk, err = lock.LockedOpenFile(preparePath(fsFormatPath), os.O_RDWR|os.O_CREATE, 0600) + if err != nil { + t.Fatal(err) + } + + formatCfg := &formatConfigV1{} + _, err = formatCfg.ReadFrom(lk) + lk.Close() + if err != nil { + t.Fatal(err) + } + err = formatCfg.CheckFS() + if err != nil && testCase.shouldPass { + t.Errorf("Test %d: Should not fail with unexpected %s, expected nil", i+1, err) + } + if err == nil && !testCase.shouldPass { + t.Errorf("Test %d: Should fail with expected %s, got nil", i+1, testCase.formatCheckErr) + } + if err != nil && !testCase.shouldPass { + if errorCause(err).Error() != testCase.formatCheckErr.Error() { + t.Errorf("Test %d: Should fail with expected %s, got %s", i+1, testCase.formatCheckErr, err) + } + } + } +} + +// TestFSCheckFormatFS - test loadFormatFS with healty and faulty disks +func TestFSCheckFormatFS(t *testing.T) { + // Prepare for testing + disk := filepath.Join(globalTestTmpDir, "minio-"+nextSuffix()) + defer removeAll(disk) + + // Assign a new UUID. + uuid := mustGetUUID() + + // Initialize meta volume, if volume already exists ignores it. + if err := initMetaVolumeFS(disk, uuid); err != nil { + t.Fatal(err) + } + + fsFormatPath := pathJoin(disk, minioMetaBucket, formatConfigFile) + lk, err := lock.LockedOpenFile(preparePath(fsFormatPath), os.O_RDWR|os.O_CREATE, 0600) + if err != nil { + t.Fatal(err) + } + + format := newFSFormatV1() + _, err = format.WriteTo(lk) + lk.Close() + if err != nil { + t.Fatal(err) + } + + // Loading corrupted format file + file, err := os.OpenFile(preparePath(fsFormatPath), os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0666) + if err != nil { + t.Fatal("Should not fail here", err) + } + file.Write([]byte{'b'}) + file.Close() + + lk, err = lock.LockedOpenFile(preparePath(fsFormatPath), os.O_RDWR|os.O_CREATE, 0600) + if err != nil { + t.Fatal(err) + } + + format = &formatConfigV1{} + _, err = format.ReadFrom(lk) + lk.Close() + if err == nil { + t.Fatal("Should return an error here") + } + + // Loading format file from disk not found. + removeAll(disk) + _, err = lock.LockedOpenFile(preparePath(fsFormatPath), os.O_RDONLY, 0600) + if err != nil && !os.IsNotExist(err) { + t.Fatal("Should return 'format.json' does not exist, but got", err) + } +} + func TestLoadFormatXLErrs(t *testing.T) { nDisks := 16 fsDirs, err := getRandomDisks(nDisks) @@ -749,7 +911,7 @@ func TestLoadFormatXLErrs(t *testing.T) { // disks 0..10 returns unformatted disk for i := 0; i <= 10; i++ { - if err = xl.storageDisks[i].DeleteFile(".minio.sys", "format.json"); err != nil { + if err = xl.storageDisks[i].DeleteFile(minioMetaBucket, formatConfigFile); err != nil { t.Fatal(err) } } @@ -873,7 +1035,7 @@ func TestHealFormatXLCorruptedDisksErrs(t *testing.T) { } xl = obj.(*xlObjects) for i := 0; i <= 15; i++ { - if err = xl.storageDisks[i].DeleteFile(".minio.sys", "format.json"); err != nil { + if err = xl.storageDisks[i].DeleteFile(minioMetaBucket, formatConfigFile); err != nil { t.Fatal(err) } } @@ -894,7 +1056,7 @@ func TestHealFormatXLCorruptedDisksErrs(t *testing.T) { } xl = obj.(*xlObjects) for i := 0; i <= 15; i++ { - if err = xl.storageDisks[i].AppendFile(".minio.sys", "format.json", []byte("corrupted data")); err != nil { + if err = xl.storageDisks[i].AppendFile(minioMetaBucket, formatConfigFile, []byte("corrupted data")); err != nil { t.Fatal(err) } } @@ -998,7 +1160,7 @@ func TestHealFormatXLFreshDisksErrs(t *testing.T) { } xl = obj.(*xlObjects) for i := 0; i <= 15; i++ { - if err = xl.storageDisks[i].DeleteFile(".minio.sys", "format.json"); err != nil { + if err = xl.storageDisks[i].DeleteFile(minioMetaBucket, formatConfigFile); err != nil { t.Fatal(err) } } diff --git a/cmd/fs-v1-helpers.go b/cmd/fs-v1-helpers.go index 002eda887..e55034f16 100644 --- a/cmd/fs-v1-helpers.go +++ b/cmd/fs-v1-helpers.go @@ -134,6 +134,7 @@ func fsStat(statLoc string) (os.FileInfo, error) { if err != nil { return nil, traceError(err) } + return fi, nil } @@ -142,12 +143,13 @@ func fsStat(statLoc string) (os.FileInfo, error) { func fsStatDir(statDir string) (os.FileInfo, error) { fi, err := fsStat(statDir) if err != nil { - if os.IsNotExist(errorCause(err)) { + err = errorCause(err) + if os.IsNotExist(err) { return nil, traceError(errVolumeNotFound) - } else if os.IsPermission(errorCause(err)) { + } else if os.IsPermission(err) { return nil, traceError(errVolumeAccessDenied) } - return nil, err + return nil, traceError(err) } if !fi.IsDir() { @@ -161,16 +163,17 @@ func fsStatDir(statDir string) (os.FileInfo, error) { func fsStatFile(statFile string) (os.FileInfo, error) { fi, err := fsStat(statFile) if err != nil { - if os.IsNotExist(errorCause(err)) { + err = errorCause(err) + if os.IsNotExist(err) { return nil, traceError(errFileNotFound) - } else if os.IsPermission(errorCause(err)) { + } else if os.IsPermission(err) { return nil, traceError(errFileAccessDenied) - } else if isSysErrNotDir(errorCause(err)) { + } else if isSysErrNotDir(err) { return nil, traceError(errFileAccessDenied) - } else if isSysErrPathNotFound(errorCause(err)) { + } else if isSysErrPathNotFound(err) { return nil, traceError(errFileNotFound) } - return nil, err + return nil, traceError(err) } if fi.IsDir() { return nil, traceError(errFileAccessDenied) diff --git a/cmd/fs-v1-metadata.go b/cmd/fs-v1-metadata.go index fdbc4eaf4..29c595ed2 100644 --- a/cmd/fs-v1-metadata.go +++ b/cmd/fs-v1-metadata.go @@ -18,16 +18,14 @@ package cmd import ( "encoding/json" - "errors" - "fmt" "io" "io/ioutil" "os" pathutil "path" "sort" "strings" + "time" - "github.com/minio/minio-go/pkg/set" "github.com/minio/minio/pkg/lock" "github.com/minio/minio/pkg/mimedb" "github.com/tidwall/gjson" @@ -37,8 +35,6 @@ import ( const ( // fs.json object metadata. fsMetaJSONFile = "fs.json" - // format.json FS format metadata. - fsFormatJSONFile = "format.json" ) // FS metadata constants. @@ -52,9 +48,6 @@ const ( // FS backend meta format. fsMetaFormat = "fs" - // FS backend format version. - fsFormatVersion = fsFormatV2 - // Add more constants here. ) @@ -257,14 +250,6 @@ func (m *fsMetaV1) ReadFrom(lk *lock.LockedFile) (n int64, err error) { return int64(len(fsMetaBuf)), nil } -// FS format version strings. -const ( - fsFormatV1 = "1" // Previous format. - fsFormatV2 = "2" // Current format. - // Proceed to add "3" when we - // change the backend format in future. -) - // newFSMetaV1 - initializes new fsMetaV1. func newFSMetaV1() (fsMeta fsMetaV1) { fsMeta = fsMetaV1{} @@ -274,167 +259,107 @@ func newFSMetaV1() (fsMeta fsMetaV1) { return fsMeta } -// newFSFormatV2 - initializes new formatConfigV1 with FS format version 2. -func newFSFormatV2() (format *formatConfigV1) { - return &formatConfigV1{ - Version: "1", - Format: "fs", - FS: &fsFormat{ - Version: fsFormatV2, - }, - } -} +// Check if disk has already a valid format, holds a read lock and +// upon success returns it to the caller to be closed. +func checkLockedValidFormatFS(fsPath string) (*lock.RLockedFile, error) { + fsFormatPath := pathJoin(fsPath, minioMetaBucket, formatConfigFile) -// Checks if input format is version 1 and 2. -func isFSValidFormat(formatCfg *formatConfigV1) bool { - // Supported format versions. - var supportedFormatVersions = []string{ - fsFormatV1, - fsFormatV2, - // New supported versions here. - } - - // Check for supported format versions. - for _, version := range supportedFormatVersions { - if formatCfg.FS.Version == version { - return true + rlk, err := lock.RLockedOpenFile(preparePath(fsFormatPath)) + if err != nil { + if os.IsNotExist(err) { + // If format.json not found then + // its an unformatted disk. + return nil, traceError(errUnformattedDisk) } + return nil, traceError(err) } - return false + + var format = &formatConfigV1{} + if err = format.LoadFormat(rlk.LockedFile); err != nil { + rlk.Close() + return nil, err + } + + // Check format FS. + if err = format.CheckFS(); err != nil { + rlk.Close() + return nil, err + } + + // Always return read lock here and should be closed by the caller. + return rlk, traceError(err) } -// errFSFormatOld- old fs format. -var errFSFormatOld = errors.New("old FS format found") +// Writes the new format.json if unformatted, +// otherwise closes the input locked file +// and returns any error. +func writeFormatFS(lk *lock.LockedFile) error { + // Close the locked file upon return. + defer lk.Close() -// Checks if the loaded `format.json` is valid and -// is expected to be of the requested version. -func checkFormatFS(format *formatConfigV1, formatVersion string) error { - if format == nil { - return errUnexpected - } - - // Validate if we have the same format. - if format.Format != "fs" { - return fmt.Errorf("Unable to recognize backend format, Disk is not in FS format. %s", format.Format) - } - - // Check if format is currently supported. - if !isFSValidFormat(format) { - return errCorruptedFormat - } - - // Check for format version is current. - if format.FS.Version != formatVersion { - return errFSFormatOld - } - - return nil -} - -// This is just kept as reference, there is no sanity -// check for FS format in version "1". -func checkFormatSanityFSV1(fsPath string) error { - return nil -} - -// Check for sanity of FS format in version "2". -func checkFormatSanityFSV2(fsPath string) error { - buckets, err := readDir(pathJoin(fsPath, minioMetaBucket, bucketConfigPrefix)) - if err != nil && err != errFileNotFound { + // Load format on disk, checks if we are unformatted + // writes the new format.json + var format = &formatConfigV1{} + err := format.LoadFormat(lk) + if errorCause(err) == errUnformattedDisk { + _, err = newFSFormat().WriteTo(lk) return err } - - // Attempt to validate all the buckets have a sanitized backend. - for _, bucket := range buckets { - entries, rerr := readDir(pathJoin(fsPath, minioMetaBucket, bucketConfigPrefix, bucket)) - if rerr != nil { - return rerr - } - - var expectedConfigs = append(bucketMetadataConfigs, objectMetaPrefix+"/") - entriesSet := set.CreateStringSet(entries...) - expectedConfigsSet := set.CreateStringSet(expectedConfigs...) - - // Entries found shouldn't be more than total - // expected config directories, files. - if len(entriesSet) > len(expectedConfigsSet) { - return errCorruptedFormat - } - - // Look for the difference between entries and the - // expected config set, resulting entries if they - // intersect with original entries set we know - // that the backend has unexpected files. - if !entriesSet.Difference(expectedConfigsSet).IsEmpty() { - return errCorruptedFormat - } - } - return nil -} - -// Check for sanity of FS format for a given version. -func checkFormatSanityFS(fsPath string, fsFormatVersion string) (err error) { - switch fsFormatVersion { - case fsFormatV2: - err = checkFormatSanityFSV2(fsPath) - default: - err = errCorruptedFormat - } return err } -// Initializes a new `format.json` if not present, validates `format.json` -// if already present and migrates to newer version if necessary. Returns -// the final format version. func initFormatFS(fsPath, fsUUID string) (err error) { - fsFormatPath := pathJoin(fsPath, minioMetaBucket, fsFormatJSONFile) + fsFormatPath := pathJoin(fsPath, minioMetaBucket, formatConfigFile) - // fsFormatJSONFile - format.json file stored in minioMetaBucket(.minio.sys) directory. - lk, err := lock.LockedOpenFile(preparePath(fsFormatPath), os.O_RDWR|os.O_CREATE, 0600) - if err != nil { - return traceError(err) - } - defer lk.Close() + // Once the filesystem has initialized hold the read lock for + // the life time of the server. This is done to ensure that under + // shared backend mode for FS, remote servers do not migrate + // or cause changes on backend format. - var format = &formatConfigV1{} - _, err = format.ReadFrom(lk) - // For all unexpected errors, we return. - if err != nil && errorCause(err) != io.EOF { - return traceError(fmt.Errorf("Unable to load 'format.json', %s", err)) - } + // This loop validates format.json by holding a read lock and + // proceeds if disk unformatted to hold non-blocking WriteLock + // If for some reason non-blocking WriteLock fails and the error + // is lock.ErrAlreadyLocked i.e some other process is holding a + // lock we retry in the loop again. + var rlk *lock.RLockedFile + for { + // Validate the `format.json` for expected values. + rlk, err = checkLockedValidFormatFS(fsPath) + switch { + case err == nil: + // Holding a read lock ensures that any write lock operation + // is blocked if attempted in-turn avoiding corruption on + // the backend disk. + _ = rlk // Hold the lock on `format.json` until server dies. + return nil + case errorCause(err) == errUnformattedDisk: + // Attempt a write lock on formatConfigFile `format.json` + // file stored in minioMetaBucket(.minio.sys) directory. + var lk *lock.LockedFile + lk, err = lock.TryLockedOpenFile(preparePath(fsFormatPath), os.O_RDWR|os.O_CREATE, 0600) + if err != nil { + // Existing write locks detected. + if err == lock.ErrAlreadyLocked { + // Lock already present, sleep and attempt again. + time.Sleep(100 * time.Millisecond) + continue + } - // If we couldn't read anything, The disk is unformatted. - if errorCause(err) == io.EOF { - err = errUnformattedDisk - format = newFSFormatV2() - } else { - // Validate loaded `format.json`. - err = checkFormatFS(format, fsFormatVersion) - if err != nil && err != errFSFormatOld { - return traceError(fmt.Errorf("Unable to validate 'format.json', %s", err)) + // Unexpected error, return. + return traceError(err) + } + + // Write new format. + if err = writeFormatFS(lk); err != nil { + return err + } + // Loop will continue to attempt a + // read-lock on `format.json` . + default: + // Unhandled error return. + return err } } - - // Disk is in old format migrate object metadata. - if err == errFSFormatOld { - if merr := migrateFSObject(fsPath, fsUUID); merr != nil { - return merr - } - - // Initialize format v2. - format = newFSFormatV2() - } - - // Rewrite or write format.json depending on if disk - // unformatted and if format is old. - if err == errUnformattedDisk || err == errFSFormatOld { - if _, err = format.WriteTo(lk); err != nil { - return traceError(fmt.Errorf("Unable to initialize 'format.json', %s", err)) - } - } - - // Check for sanity. - return checkFormatSanityFS(fsPath, format.FS.Version) } // Return if the part info in uploadedParts and completeParts are same. diff --git a/cmd/fs-v1-metadata_test.go b/cmd/fs-v1-metadata_test.go index 1e71acb63..fcc498502 100644 --- a/cmd/fs-v1-metadata_test.go +++ b/cmd/fs-v1-metadata_test.go @@ -58,7 +58,7 @@ func TestReadFSMetadata(t *testing.T) { } // Construct the full path of fs.json - fsPath := pathJoin(bucketMetaPrefix, bucketName, objectMetaPrefix, objectName, "fs.json") + fsPath := pathJoin(bucketMetaPrefix, bucketName, objectName, "fs.json") fsPath = pathJoin(fs.fsPath, minioMetaBucket, fsPath) rlk, err := fs.rwPool.Open(fsPath) @@ -95,7 +95,7 @@ func TestWriteFSMetadata(t *testing.T) { } // Construct the full path of fs.json - fsPath := pathJoin(bucketMetaPrefix, bucketName, objectMetaPrefix, objectName, "fs.json") + fsPath := pathJoin(bucketMetaPrefix, bucketName, objectName, "fs.json") fsPath = pathJoin(fs.fsPath, minioMetaBucket, fsPath) rlk, err := fs.rwPool.Open(fsPath) diff --git a/cmd/fs-v1-multipart.go b/cmd/fs-v1-multipart.go index 4410fb75a..25d253714 100644 --- a/cmd/fs-v1-multipart.go +++ b/cmd/fs-v1-multipart.go @@ -759,7 +759,7 @@ func (fs fsObjects) CompleteMultipartUpload(bucket string, object string, upload // Wait for any competing PutObject() operation on bucket/object, since same namespace // would be acquired for `fs.json`. - fsMetaPath := pathJoin(fs.fsPath, minioMetaBucket, bucketMetaPrefix, bucket, objectMetaPrefix, object, fsMetaJSONFile) + fsMetaPath := pathJoin(fs.fsPath, minioMetaBucket, bucketMetaPrefix, bucket, object, fsMetaJSONFile) metaFile, err := fs.rwPool.Create(fsMetaPath) if err != nil { fs.rwPool.Close(fsMetaPathMultipart) diff --git a/cmd/fs-v1.go b/cmd/fs-v1.go index 58c8b8b85..6f9d0a9f1 100644 --- a/cmd/fs-v1.go +++ b/cmd/fs-v1.go @@ -24,7 +24,6 @@ import ( "io" "io/ioutil" "os" - "os/signal" "path" "path/filepath" "sort" @@ -74,144 +73,15 @@ func initMetaVolumeFS(fsPath, fsUUID string) error { } -// Migrate FS object is a place holder code for all -// FS format migrations. -func migrateFSObject(fsPath, fsUUID string) (err error) { - // Writing message here is important for servers being upgraded. - log.Println("Please do not stop the server.") - - ch := make(chan os.Signal) - defer signal.Stop(ch) - defer close(ch) - - signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM) - go func() { - for { - _, ok := <-ch - if !ok { - break - } - log.Println("Please wait server is being upgraded..") - } - }() - - return migrateFSFormatV1ToV2(fsPath, fsUUID) -} - -// List all buckets at meta bucket prefix in `.minio.sys/buckets/` path. -// This is implemented to avoid a bug on windows with using readDir(). -func fsReaddirMetaBuckets(fsPath string) ([]string, error) { - f, err := os.Open(preparePath(pathJoin(fsPath, minioMetaBucket, bucketConfigPrefix))) - if err != nil { - if os.IsNotExist(err) { - return nil, errFileNotFound - } else if os.IsPermission(err) { - return nil, errFileAccessDenied - } - return nil, err - } - return f.Readdirnames(-1) -} - -// List of all bucket metadata configs. -var bucketMetadataConfigs = []string{ - bucketNotificationConfig, - bucketListenerConfig, - bucketPolicyConfig, -} - -// Migrates bucket metadata configs, ignores all other files. -func migrateBucketMetadataConfigs(metaBucket, bucket, tmpBucket string) error { - for _, bucketMetaFile := range bucketMetadataConfigs { - fi, err := fsStat(pathJoin(metaBucket, tmpBucket, bucketMetaFile)) - if err != nil { - // There are no such files or directories found, - // proceed to next bucket metadata config. - if os.IsNotExist(errorCause(err)) { - continue - } - return err - } - - // Bucket metadata is a file, move it as an actual bucket config. - if fi.Mode().IsRegular() { - if err = fsRenameFile(pathJoin(metaBucket, tmpBucket, bucketMetaFile), - pathJoin(metaBucket, bucket, bucketMetaFile)); err != nil { - if errorCause(err) != errFileNotFound { - return err - } - } - } - - // All other file types are ignored. - } - - // Success. - return nil -} - -// Attempts to migrate old object metadata files to newer format -// -// i.e -// ------------------------------------------------------- -// .minio.sys/buckets///fs.json - V1 -// ------------------------------------------------------- -// .minio.sys/buckets//objects//fs.json - V2 -// ------------------------------------------------------- -// -func migrateFSFormatV1ToV2(fsPath, fsUUID string) (err error) { - metaBucket := pathJoin(fsPath, minioMetaBucket, bucketConfigPrefix) - - var buckets []string - buckets, err = fsReaddirMetaBuckets(fsPath) - if err != nil && err != errFileNotFound { - return err - } - - // Migrate all buckets present. - for _, bucket := range buckets { - // Temporary bucket of form .UUID-bucket. - tmpBucket := fmt.Sprintf(".%s-%s", fsUUID, bucket) - - // Rename existing bucket as `.UUID-bucket`. - if err = fsRenameFile(pathJoin(metaBucket, bucket), pathJoin(metaBucket, tmpBucket)); err != nil { - return err - } - - // Create a new bucket name with name as `bucket`. - if err = fsMkdir(pathJoin(metaBucket, bucket)); err != nil { - return err - } - - // Migrate all the bucket metadata configs. - if err = migrateBucketMetadataConfigs(metaBucket, bucket, tmpBucket); err != nil { - return err - } - - // Finally rename the temporary bucket to `bucket/objects` directory. - if err = fsRenameFile(pathJoin(metaBucket, tmpBucket), - pathJoin(metaBucket, bucket, objectMetaPrefix)); err != nil { - if errorCause(err) != errFileNotFound { - return err - } - } - - } - - log.Printf("Migrating bucket metadata format from \"%s\" to newer format \"%s\"... completed successfully.", fsFormatV1, fsFormatV2) - - // If all goes well we return success. - return nil -} - // newFSObjectLayer - initialize new fs object layer. func newFSObjectLayer(fsPath string) (ObjectLayer, error) { if fsPath == "" { return nil, errInvalidArgument } + var err error // Disallow relative paths, figure out absolute paths. - fsPath, err := filepath.Abs(fsPath) + fsPath, err = filepath.Abs(fsPath) if err != nil { return nil, err } @@ -257,12 +127,6 @@ func newFSObjectLayer(fsPath string) (ObjectLayer, error) { return nil, err } - // Once initialized hold read lock for the entire operation - // of filesystem backend. - if _, err = fs.rwPool.Open(pathJoin(fsPath, minioMetaBucket, fsFormatJSONFile)); err != nil { - return nil, err - } - // Initialize and load bucket policies. err = initBucketPolicies(fs) if err != nil { @@ -281,9 +145,6 @@ func newFSObjectLayer(fsPath string) (ObjectLayer, error) { // Should be called when process shuts down. func (fs fsObjects) Shutdown() error { - // Close the format.json read lock. - fs.rwPool.Close(pathJoin(fs.fsPath, minioMetaBucket, fsFormatJSONFile)) - // Cleanup and delete tmp uuid. return fsRemoveAll(pathJoin(fs.fsPath, minioMetaTmpBucket, fs.fsUUID)) } @@ -363,7 +224,7 @@ func (fs fsObjects) ListBuckets() ([]BucketInfo, error) { return nil, traceError(err) } var bucketInfos []BucketInfo - entries, err := readDir(fs.fsPath) + entries, err := readDir(preparePath(fs.fsPath)) if err != nil { return nil, toObjectErr(traceError(errDiskNotFound)) } @@ -447,7 +308,7 @@ func (fs fsObjects) CopyObject(srcBucket, srcObject, dstBucket, dstObject string // Check if this request is only metadata update. cpMetadataOnly := isStringEqual(pathJoin(srcBucket, srcObject), pathJoin(dstBucket, dstObject)) if cpMetadataOnly { - fsMetaPath := pathJoin(fs.fsPath, minioMetaBucket, bucketMetaPrefix, srcBucket, objectMetaPrefix, srcObject, fsMetaJSONFile) + fsMetaPath := pathJoin(fs.fsPath, minioMetaBucket, bucketMetaPrefix, srcBucket, srcObject, fsMetaJSONFile) var wlk *lock.LockedFile wlk, err = fs.rwPool.Write(fsMetaPath) if err != nil { @@ -520,7 +381,7 @@ func (fs fsObjects) GetObject(bucket, object string, offset int64, length int64, } if bucket != minioMetaBucket { - fsMetaPath := pathJoin(fs.fsPath, minioMetaBucket, bucketMetaPrefix, bucket, objectMetaPrefix, object, fsMetaJSONFile) + fsMetaPath := pathJoin(fs.fsPath, minioMetaBucket, bucketMetaPrefix, bucket, object, fsMetaJSONFile) _, err = fs.rwPool.Open(fsMetaPath) if err != nil && err != errFileNotFound { return toObjectErr(traceError(err), bucket, object) @@ -562,7 +423,7 @@ func (fs fsObjects) GetObject(bucket, object string, offset int64, length int64, // getObjectInfo - wrapper for reading object metadata and constructs ObjectInfo. func (fs fsObjects) getObjectInfo(bucket, object string) (ObjectInfo, error) { fsMeta := fsMetaV1{} - fsMetaPath := pathJoin(fs.fsPath, minioMetaBucket, bucketMetaPrefix, bucket, objectMetaPrefix, object, fsMetaJSONFile) + fsMetaPath := pathJoin(fs.fsPath, minioMetaBucket, bucketMetaPrefix, bucket, object, fsMetaJSONFile) // Read `fs.json` to perhaps contend with // parallel Put() operations. @@ -669,7 +530,7 @@ func (fs fsObjects) PutObject(bucket string, object string, size int64, data io. var wlk *lock.LockedFile if bucket != minioMetaBucket { bucketMetaDir := pathJoin(fs.fsPath, minioMetaBucket, bucketMetaPrefix) - fsMetaPath := pathJoin(bucketMetaDir, bucket, objectMetaPrefix, object, fsMetaJSONFile) + fsMetaPath := pathJoin(bucketMetaDir, bucket, object, fsMetaJSONFile) wlk, err = fs.rwPool.Create(fsMetaPath) if err != nil { return ObjectInfo{}, toObjectErr(traceError(err), bucket, object) @@ -796,7 +657,7 @@ func (fs fsObjects) DeleteObject(bucket, object string) error { } minioMetaBucketDir := pathJoin(fs.fsPath, minioMetaBucket) - fsMetaPath := pathJoin(minioMetaBucketDir, bucketMetaPrefix, bucket, objectMetaPrefix, object, fsMetaJSONFile) + fsMetaPath := pathJoin(minioMetaBucketDir, bucketMetaPrefix, bucket, object, fsMetaJSONFile) if bucket != minioMetaBucket { rwlk, lerr := fs.rwPool.Write(fsMetaPath) if lerr == nil { @@ -850,7 +711,7 @@ func (fs fsObjects) listDirFactory(isLeaf isLeafFunc) listDirFunc { // getObjectETag is a helper function, which returns only the md5sum // of the file on the disk. func (fs fsObjects) getObjectETag(bucket, entry string) (string, error) { - fsMetaPath := pathJoin(fs.fsPath, minioMetaBucket, bucketMetaPrefix, bucket, objectMetaPrefix, entry, fsMetaJSONFile) + fsMetaPath := pathJoin(fs.fsPath, minioMetaBucket, bucketMetaPrefix, bucket, entry, fsMetaJSONFile) // Read `fs.json` to perhaps contend with // parallel Put() operations. diff --git a/cmd/fs-v1_test.go b/cmd/fs-v1_test.go index 6c2a4b6a1..f60ff97d0 100644 --- a/cmd/fs-v1_test.go +++ b/cmd/fs-v1_test.go @@ -21,10 +21,7 @@ import ( "fmt" "os" "path/filepath" - "strings" "testing" - - "github.com/minio/minio/pkg/lock" ) // TestNewFS - tests initialization of all input disks @@ -88,516 +85,6 @@ func TestFSShutdown(t *testing.T) { } } -// Tests migrating FS format without .minio.sys/buckets. -func TestFSMigrateObjectWithoutObjects(t *testing.T) { - // Prepare for testing - disk := filepath.Join(globalTestTmpDir, "minio-"+nextSuffix()) - defer removeAll(disk) - - // Assign a new UUID. - uuid := mustGetUUID() - - // Initialize meta volume, if volume already exists ignores it. - if err := initMetaVolumeFS(disk, uuid); err != nil { - t.Fatal(err) - } - - fsFormatPath := pathJoin(disk, minioMetaBucket, fsFormatJSONFile) - formatCfg := &formatConfigV1{ - Version: "1", - Format: "fs", - FS: &fsFormat{ - Version: "1", - }, - } - - lk, err := lock.LockedOpenFile(preparePath(fsFormatPath), os.O_RDWR|os.O_CREATE, 0600) - if err != nil { - t.Fatal(err) - } - _, err = formatCfg.WriteTo(lk) - lk.Close() - if err != nil { - t.Fatal("Should not fail here", err) - } - - if err = initFormatFS(disk, uuid); err != nil { - t.Fatal("Should not fail with unexpected", err) - } - - formatCfg = &formatConfigV1{} - lk, err = lock.LockedOpenFile(preparePath(fsFormatPath), os.O_RDONLY, 0600) - if err != nil { - t.Fatal(err) - } - _, err = formatCfg.ReadFrom(lk) - lk.Close() - if err != nil { - t.Fatal("Should not fail here", err) - } - if formatCfg.FS.Version != fsFormatV2 { - t.Fatalf("Unexpected version detected expected \"%s\", got %s", fsFormatV2, formatCfg.FS.Version) - } -} - -// Tests migrating FS format without .minio.sys/buckets. -func TestFSMigrateObjectWithErr(t *testing.T) { - // Prepare for testing - disk := filepath.Join(globalTestTmpDir, "minio-"+nextSuffix()) - defer removeAll(disk) - - // Assign a new UUID. - uuid := mustGetUUID() - - // Initialize meta volume, if volume already exists ignores it. - if err := initMetaVolumeFS(disk, uuid); err != nil { - t.Fatal(err) - } - - fsFormatPath := pathJoin(disk, minioMetaBucket, fsFormatJSONFile) - formatCfg := &formatConfigV1{ - Version: "1", - Format: "fs", - FS: &fsFormat{ - Version: "10", - }, - } - - lk, err := lock.LockedOpenFile(preparePath(fsFormatPath), os.O_RDWR|os.O_CREATE, 0600) - if err != nil { - t.Fatal(err) - } - _, err = formatCfg.WriteTo(lk) - lk.Close() - if err != nil { - t.Fatal("Should not fail here", err) - } - - if err = initFormatFS(disk, uuid); err != nil { - if !strings.Contains(errorCause(err).Error(), "Unable to validate 'format.json', corrupted backend format") { - t.Fatal("Should not fail with unexpected", err) - } - } - - fsFormatPath = pathJoin(disk, minioMetaBucket, fsFormatJSONFile) - formatCfg = &formatConfigV1{ - Version: "1", - Format: "garbage", - FS: &fsFormat{ - Version: "1", - }, - } - - lk, err = lock.LockedOpenFile(preparePath(fsFormatPath), os.O_RDWR|os.O_CREATE, 0600) - if err != nil { - t.Fatal(err) - } - _, err = formatCfg.WriteTo(lk) - lk.Close() - if err != nil { - t.Fatal("Should not fail here", err) - } - - if err = initFormatFS(disk, uuid); err != nil { - if errorCause(err).Error() != - "Unable to validate 'format.json', Unable to recognize backend format, Disk is not in FS format. garbage" { - t.Fatal("Should not fail with unexpected", err) - } - } - -} - -// Tests migrating FS format with .minio.sys/buckets filled with -// objects such as policy.json/fs.json, notification.xml/fs.json -// listener.json/fs.json. -func TestFSMigrateObjectWithBucketConfigObjects(t *testing.T) { - // Prepare for testing - disk := filepath.Join(globalTestTmpDir, "minio-"+nextSuffix()) - defer removeAll(disk) - - // Assign a new UUID. - uuid := mustGetUUID() - - // Initialize meta volume, if volume already exists ignores it. - if err := initMetaVolumeFS(disk, uuid); err != nil { - t.Fatal(err) - } - - fsFormatPath := pathJoin(disk, minioMetaBucket, fsFormatJSONFile) - formatCfg := &formatConfigV1{ - Version: "1", - Format: "fs", - FS: &fsFormat{ - Version: "1", - }, - } - lk, err := lock.LockedOpenFile(preparePath(fsFormatPath), os.O_RDWR|os.O_CREATE, 0600) - if err != nil { - t.Fatal(err) - } - _, err = formatCfg.WriteTo(lk) - lk.Close() - if err != nil { - t.Fatal("Should not fail here", err) - } - - // Construct the full path of fs.json - fsPath1 := pathJoin(bucketMetaPrefix, "testvolume1", bucketPolicyConfig, fsMetaJSONFile) - fsPath1 = pathJoin(disk, minioMetaBucket, fsPath1) - - fsMetaJSON := `{"version":"1.0.0","format":"fs","minio":{"release":"DEVELOPMENT.2017-03-27T02-26-33Z"},"meta":{"etag":"467886be95c8ecfd71a2900e3f461b4f"}` - if _, err = fsCreateFile(fsPath1, bytes.NewReader([]byte(fsMetaJSON)), nil, 0); err != nil { - t.Fatal(err) - } - - // Construct the full path of fs.json - fsPath2 := pathJoin(bucketMetaPrefix, "testvolume2", bucketNotificationConfig, fsMetaJSONFile) - fsPath2 = pathJoin(disk, minioMetaBucket, fsPath2) - - fsMetaJSON = `{"version":"1.0.0","format":"fs","minio":{"release":"DEVELOPMENT.2017-03-27T02-26-33Z"},"meta":{"etag":"467886be95c8ecfd71a2900eff461b4d"}` - if _, err = fsCreateFile(fsPath2, bytes.NewReader([]byte(fsMetaJSON)), nil, 0); err != nil { - t.Fatal(err) - } - - // Construct the full path of fs.json - fsPath3 := pathJoin(bucketMetaPrefix, "testvolume3", bucketListenerConfig, fsMetaJSONFile) - fsPath3 = pathJoin(disk, minioMetaBucket, fsPath3) - - fsMetaJSON = `{"version":"1.0.0","format":"fs","minio":{"release":"DEVELOPMENT.2017-03-27T02-26-33Z"},"meta":{"etag":"467886be95c8ecfd71a2900eff461b4d"}` - if _, err = fsCreateFile(fsPath3, bytes.NewReader([]byte(fsMetaJSON)), nil, 0); err != nil { - t.Fatal(err) - } - - if err = initFormatFS(disk, mustGetUUID()); err != nil { - t.Fatal("Should not fail here", err) - } - - fsPath1 = pathJoin(bucketMetaPrefix, "testvolume1", objectMetaPrefix, bucketPolicyConfig, fsMetaJSONFile) - fsPath1 = pathJoin(disk, minioMetaBucket, fsPath1) - fi, err := fsStatFile(fsPath1) - if err != nil { - t.Fatal("Path should exist and accessible after migration", err) - } - if fi.IsDir() { - t.Fatalf("Unexpected path %s should be a file", fsPath1) - } - - fsPath2 = pathJoin(bucketMetaPrefix, "testvolume2", objectMetaPrefix, bucketNotificationConfig, fsMetaJSONFile) - fsPath2 = pathJoin(disk, minioMetaBucket, fsPath2) - fi, err = fsStatFile(fsPath2) - if err != nil { - t.Fatal("Path should exist and accessible after migration", err) - } - if fi.IsDir() { - t.Fatalf("Unexpected path %s should be a file", fsPath2) - } - - fsPath3 = pathJoin(bucketMetaPrefix, "testvolume3", objectMetaPrefix, bucketListenerConfig, fsMetaJSONFile) - fsPath3 = pathJoin(disk, minioMetaBucket, fsPath3) - fi, err = fsStatFile(fsPath3) - if err != nil { - t.Fatal("Path should exist and accessible after migration", err) - } - if fi.IsDir() { - t.Fatalf("Unexpected path %s should be a file", fsPath3) - } - - formatCfg = &formatConfigV1{} - lk, err = lock.LockedOpenFile(preparePath(fsFormatPath), os.O_RDONLY, 0600) - if err != nil { - t.Fatal(err) - } - _, err = formatCfg.ReadFrom(lk) - lk.Close() - if err != nil { - t.Fatal("Should not fail here", err) - } - if formatCfg.FS.Version != fsFormatV2 { - t.Fatalf("Unexpected version detected expected \"%s\", got %s", fsFormatV2, formatCfg.FS.Version) - } -} - -// Tests migrating FS format with .minio.sys/buckets filled with -// object metadata. -func TestFSMigrateObjectWithObjects(t *testing.T) { - // Prepare for testing - disk := filepath.Join(globalTestTmpDir, "minio-"+nextSuffix()) - defer removeAll(disk) - - // Assign a new UUID. - uuid := mustGetUUID() - - // Initialize meta volume, if volume already exists ignores it. - if err := initMetaVolumeFS(disk, uuid); err != nil { - t.Fatal(err) - } - - fsFormatPath := pathJoin(disk, minioMetaBucket, fsFormatJSONFile) - formatCfg := &formatConfigV1{ - Version: "1", - Format: "fs", - FS: &fsFormat{ - Version: "1", - }, - } - lk, err := lock.LockedOpenFile(preparePath(fsFormatPath), os.O_RDWR|os.O_CREATE, 0600) - if err != nil { - t.Fatal(err) - } - _, err = formatCfg.WriteTo(lk) - lk.Close() - if err != nil { - t.Fatal("Should not fail here", err) - } - - // Construct the full path of fs.json - fsPath1 := pathJoin(bucketMetaPrefix, "testvolume1", "my-object1", fsMetaJSONFile) - fsPath1 = pathJoin(disk, minioMetaBucket, fsPath1) - - fsMetaJSON := `{"version":"1.0.0","format":"fs","minio":{"release":"DEVELOPMENT.2017-03-27T02-26-33Z"},"meta":{"etag":"467886be95c8ecfd71a2900e3f461b4f"}` - if _, err = fsCreateFile(fsPath1, bytes.NewReader([]byte(fsMetaJSON)), nil, 0); err != nil { - t.Fatal(err) - } - - // Construct the full path of fs.json - fsPath2 := pathJoin(bucketMetaPrefix, "testvolume2", "my-object2", fsMetaJSONFile) - fsPath2 = pathJoin(disk, minioMetaBucket, fsPath2) - - fsMetaJSON = `{"version":"1.0.0","format":"fs","minio":{"release":"DEVELOPMENT.2017-03-27T02-26-33Z"},"meta":{"etag":"467886be95c8ecfd71a2900eff461b4d"}` - if _, err = fsCreateFile(fsPath2, bytes.NewReader([]byte(fsMetaJSON)), nil, 0); err != nil { - t.Fatal(err) - } - - // Construct the full path of policy.json - ppath := pathJoin(bucketMetaPrefix, "testvolume2", bucketPolicyConfig) - ppath = pathJoin(disk, minioMetaBucket, ppath) - - policyJSON := `{"Version":"2012-10-17","Statement":[{"Action":["s3:GetBucketLocation","s3:ListBucket"],"Effect":"Allow","Principal":{"AWS":["*"]},"Resource":["arn:aws:s3:::testbucket"],"Sid":""},{"Action":["s3:GetObject"],"Effect":"Allow","Principal":{"AWS":["*"]},"Resource":["arn:aws:s3:::testbucket/*"],"Sid":""}]}` - if _, err = fsCreateFile(ppath, bytes.NewReader([]byte(policyJSON)), nil, 0); err != nil { - t.Fatal(err) - } - - if err = initFormatFS(disk, mustGetUUID()); err != nil { - t.Fatal("Should not fail here", err) - } - - fsPath2 = pathJoin(bucketMetaPrefix, "testvolume2", objectMetaPrefix, "my-object2", fsMetaJSONFile) - fsPath2 = pathJoin(disk, minioMetaBucket, fsPath2) - fi, err := fsStatFile(fsPath2) - if err != nil { - t.Fatal("Path should exist and accessible after migration", err) - } - if fi.IsDir() { - t.Fatalf("Unexpected path %s should be a file", fsPath2) - } - - formatCfg = &formatConfigV1{} - lk, err = lock.LockedOpenFile(preparePath(fsFormatPath), os.O_RDONLY, 0600) - if err != nil { - t.Fatal(err) - } - _, err = formatCfg.ReadFrom(lk) - lk.Close() - if err != nil { - t.Fatal("Should not fail here", err) - } - if formatCfg.FS.Version != fsFormatV2 { - t.Fatalf("Unexpected version detected expected \"%s\", got %s", fsFormatV2, formatCfg.FS.Version) - } - - ppath = pathJoin(bucketMetaPrefix, "testvolume2", "acl.json") - ppath = pathJoin(disk, minioMetaBucket, ppath) - - if _, err = fsCreateFile(ppath, bytes.NewReader([]byte("")), nil, 0); err != nil { - t.Fatal(err) - } - - if err = initFormatFS(disk, mustGetUUID()); errorCause(err) != errCorruptedFormat { - t.Fatal("Should not fail here", err) - } -} - -// TestFSCheckFormatFSErr - test loadFormatFS loading older format. -func TestFSCheckFormatFSErr(t *testing.T) { - // Prepare for testing - disk := filepath.Join(globalTestTmpDir, "minio-"+nextSuffix()) - defer removeAll(disk) - - // Assign a new UUID. - uuid := mustGetUUID() - - // Initialize meta volume, if volume already exists ignores it. - if err := initMetaVolumeFS(disk, uuid); err != nil { - t.Fatal(err) - } - - fsFormatPath := pathJoin(disk, minioMetaBucket, fsFormatJSONFile) - formatCfg := &formatConfigV1{ - Version: "1", - Format: "fs", - FS: &fsFormat{ - Version: "1", - }, - } - - lk, err := lock.LockedOpenFile(preparePath(fsFormatPath), os.O_RDWR|os.O_CREATE, 0600) - if err != nil { - t.Fatal(err) - } - - _, err = formatCfg.WriteTo(lk) - lk.Close() - if err != nil { - t.Fatal(err) - } - - formatCfg = &formatConfigV1{} - lk, err = lock.LockedOpenFile(preparePath(fsFormatPath), os.O_RDWR|os.O_CREATE, 0600) - if err != nil { - t.Fatal(err) - } - _, err = formatCfg.ReadFrom(lk) - lk.Close() - if err != nil { - t.Fatal(err) - } - - if err = checkFormatFS(formatCfg, fsFormatVersion); errorCause(err) != errFSFormatOld { - t.Fatal("Should not fail with unexpected", err) - } - - formatCfg = &formatConfigV1{ - Version: "1", - Format: "fs", - FS: &fsFormat{ - Version: "10", - }, - } - - lk, err = lock.LockedOpenFile(preparePath(fsFormatPath), os.O_RDWR|os.O_CREATE, 0600) - if err != nil { - t.Fatal(err) - } - - _, err = formatCfg.WriteTo(lk) - lk.Close() - if err != nil { - t.Fatal(err) - } - - if err = checkFormatFS(formatCfg, fsFormatVersion); errorCause(err) != errCorruptedFormat { - t.Fatal("Should not fail with unexpected", err) - } - - formatCfg = &formatConfigV1{ - Version: "1", - Format: "garbage", - FS: &fsFormat{ - Version: "1", - }, - } - - lk, err = lock.LockedOpenFile(preparePath(fsFormatPath), os.O_RDWR|os.O_CREATE, 0600) - if err != nil { - t.Fatal(err) - } - - _, err = formatCfg.WriteTo(lk) - lk.Close() - if err != nil { - t.Fatal(err) - } - - if err = checkFormatFS(formatCfg, fsFormatVersion); err != nil { - if errorCause(err).Error() != "Unable to recognize backend format, Disk is not in FS format. garbage" { - t.Fatal("Should not fail with unexpected", err) - } - } - - if err = checkFormatFS(nil, fsFormatVersion); errorCause(err) != errUnexpected { - t.Fatal("Should fail with errUnexpected, but found", err) - } - - formatCfg = &formatConfigV1{ - Version: "1", - Format: "fs", - FS: &fsFormat{ - Version: "2", - }, - } - - lk, err = lock.LockedOpenFile(preparePath(fsFormatPath), os.O_RDWR|os.O_CREATE, 0600) - if err != nil { - t.Fatal(err) - } - - _, err = formatCfg.WriteTo(lk) - lk.Close() - if err != nil { - t.Fatal(err) - } - - // Should not fail. - if err = checkFormatFS(formatCfg, fsFormatVersion); err != nil { - t.Fatal(err) - } -} - -// TestFSCheckFormatFS - test loadFormatFS with healty and faulty disks -func TestFSCheckFormatFS(t *testing.T) { - // Prepare for testing - disk := filepath.Join(globalTestTmpDir, "minio-"+nextSuffix()) - defer removeAll(disk) - - // Assign a new UUID. - uuid := mustGetUUID() - - // Initialize meta volume, if volume already exists ignores it. - if err := initMetaVolumeFS(disk, uuid); err != nil { - t.Fatal(err) - } - - fsFormatPath := pathJoin(disk, minioMetaBucket, fsFormatJSONFile) - lk, err := lock.LockedOpenFile(preparePath(fsFormatPath), os.O_RDWR|os.O_CREATE, 0600) - if err != nil { - t.Fatal(err) - } - - format := newFSFormatV2() - _, err = format.WriteTo(lk) - lk.Close() - if err != nil { - t.Fatal(err) - } - - // Loading corrupted format file - file, err := os.OpenFile(preparePath(fsFormatPath), os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0666) - if err != nil { - t.Fatal("Should not fail here", err) - } - file.Write([]byte{'b'}) - file.Close() - - lk, err = lock.LockedOpenFile(preparePath(fsFormatPath), os.O_RDWR|os.O_CREATE, 0600) - if err != nil { - t.Fatal(err) - } - - format = &formatConfigV1{} - _, err = format.ReadFrom(lk) - lk.Close() - if err == nil { - t.Fatal("Should return an error here") - } - - // Loading format file from disk not found. - removeAll(disk) - _, err = lock.LockedOpenFile(preparePath(fsFormatPath), os.O_RDONLY, 0600) - if err != nil && !os.IsNotExist(err) { - t.Fatal("Should return 'format.json' does not exist, but got", err) - } -} - // TestFSGetBucketInfo - test GetBucketInfo with healty and faulty disks func TestFSGetBucketInfo(t *testing.T) { // Prepare for testing @@ -633,7 +120,6 @@ func TestFSGetBucketInfo(t *testing.T) { } } -// Tests FS backend put object behavior. func TestFSPutObject(t *testing.T) { // Prepare for tests disk := filepath.Join(globalTestTmpDir, "minio-"+nextSuffix()) diff --git a/cmd/object-api-common.go b/cmd/object-api-common.go index 506a91ec7..1b26dc694 100644 --- a/cmd/object-api-common.go +++ b/cmd/object-api-common.go @@ -32,9 +32,6 @@ const ( // Buckets meta prefix. bucketMetaPrefix = "buckets" - // Objects meta prefix. - objectMetaPrefix = "objects" - // ETag (hex encoded md5sum) of empty string. emptyETag = "d41d8cd98f00b204e9800998ecf8427e" ) diff --git a/cmd/posix-list-dir-others.go b/cmd/posix-list-dir-others.go index 2533aa064..75b53d8ca 100644 --- a/cmd/posix-list-dir-others.go +++ b/cmd/posix-list-dir-others.go @@ -32,8 +32,6 @@ func readDir(dirPath string) (entries []string, err error) { // File is really not found. if os.IsNotExist(err) { return nil, errFileNotFound - } else if os.IsPermission(err) { - return nil, errFileAccessDenied } // File path cannot be verified since one of the parents is a file. diff --git a/cmd/posix.go b/cmd/posix.go index 024e8aacd..ac0c2c57d 100644 --- a/cmd/posix.go +++ b/cmd/posix.go @@ -293,7 +293,7 @@ func (s *posix) ListVols() (volsInfo []VolInfo, err error) { return nil, err } - volsInfo, err = listVols(s.diskPath) + volsInfo, err = listVols(preparePath(s.diskPath)) if err != nil { return nil, err } diff --git a/cmd/web-handlers_test.go b/cmd/web-handlers_test.go index 41dfab2a3..728b053ba 100644 --- a/cmd/web-handlers_test.go +++ b/cmd/web-handlers_test.go @@ -277,7 +277,7 @@ func testMakeBucketWebHandler(obj ObjectLayer, instanceType string, t TestErrHan {".", false}, {"ab", false}, {"minio", false}, - {".minio.sys", false}, + {minioMetaBucket, false}, {bucketName, true}, } diff --git a/cmd/xl-v1-healing.go b/cmd/xl-v1-healing.go index dcfe213e9..6bbccce7d 100644 --- a/cmd/xl-v1-healing.go +++ b/cmd/xl-v1-healing.go @@ -383,7 +383,7 @@ func healObject(storageDisks []StorageAPI, bucket string, object string, quorum for index, disk := range outDatedDisks { // Before healing outdated disks, we need to remove xl.json // and part files from "bucket/object/" so that - // rename(".minio.sys", "tmp/tmpuuid/", "bucket", "object/") succeeds. + // rename(minioMetaBucket, "tmp/tmpuuid/", "bucket", "object/") succeeds. if disk == nil { // Not an outdated disk. continue diff --git a/cmd/xl-v1-healing_test.go b/cmd/xl-v1-healing_test.go index 82853c17e..d3aa74c4f 100644 --- a/cmd/xl-v1-healing_test.go +++ b/cmd/xl-v1-healing_test.go @@ -121,7 +121,7 @@ func TestHealFormatXL(t *testing.T) { } xl = obj.(*xlObjects) for i := 0; i <= 15; i++ { - if err = xl.storageDisks[i].DeleteFile(".minio.sys", "format.json"); err != nil { + if err = xl.storageDisks[i].DeleteFile(minioMetaBucket, formatConfigFile); err != nil { t.Fatal(err) } } @@ -142,7 +142,7 @@ func TestHealFormatXL(t *testing.T) { } xl = obj.(*xlObjects) for i := 0; i <= 15; i++ { - if err = xl.storageDisks[i].AppendFile(".minio.sys", "format.json", []byte("corrupted data")); err != nil { + if err = xl.storageDisks[i].AppendFile(minioMetaBucket, formatConfigFile, []byte("corrupted data")); err != nil { t.Fatal(err) } } @@ -163,7 +163,7 @@ func TestHealFormatXL(t *testing.T) { } xl = obj.(*xlObjects) for i := 0; i <= 2; i++ { - if err = xl.storageDisks[i].DeleteFile(".minio.sys", "format.json"); err != nil { + if err = xl.storageDisks[i].DeleteFile(minioMetaBucket, formatConfigFile); err != nil { t.Fatal(err) } } @@ -184,7 +184,7 @@ func TestHealFormatXL(t *testing.T) { } xl = obj.(*xlObjects) for i := 0; i <= 2; i++ { - if err = xl.storageDisks[i].DeleteFile(".minio.sys", "format.json"); err != nil { + if err = xl.storageDisks[i].DeleteFile(minioMetaBucket, formatConfigFile); err != nil { t.Fatal(err) } } @@ -216,7 +216,7 @@ func TestHealFormatXL(t *testing.T) { t.Fatal(err) } for i := 0; i <= 2; i++ { - if err = xl.storageDisks[i].DeleteFile(".minio.sys", "format.json"); err != nil { + if err = xl.storageDisks[i].DeleteFile(minioMetaBucket, formatConfigFile); err != nil { t.Fatal(err) } } diff --git a/cmd/xl-v1.go b/cmd/xl-v1.go index f9d24417e..4d93df84c 100644 --- a/cmd/xl-v1.go +++ b/cmd/xl-v1.go @@ -29,12 +29,6 @@ import ( // XL constants. const ( - // Format config file carries backend format specific details. - formatConfigFile = "format.json" - - // Format config tmp file carries backend format. - formatConfigFileTmp = "format.json.tmp" - // XL metadata file carries per object metadata. xlMetaJSONFile = "xl.json" diff --git a/docs/shared-backend/DESIGN.md b/docs/shared-backend/DESIGN.md index 86a2aea1f..d22d3520e 100644 --- a/docs/shared-backend/DESIGN.md +++ b/docs/shared-backend/DESIGN.md @@ -81,7 +81,7 @@ An example here shows how the contention is handled with GetObject(). GetObject() holds a read lock on `fs.json`. ```go - fsMetaPath := pathJoin(fs.fsPath, minioMetaBucket, bucketMetaPrefix, bucket, objectMetaPrefix, object, fsMetaJSONFile) + fsMetaPath := pathJoin(fs.fsPath, minioMetaBucket, bucketMetaPrefix, bucket, object, fsMetaJSONFile) rlk, err := fs.rwPool.Open(fsMetaPath) if err != nil { return toObjectErr(traceError(err), bucket, object) @@ -98,7 +98,7 @@ GetObject() holds a read lock on `fs.json`. A concurrent PutObject is requested on the same object, PutObject() attempts a write lock on `fs.json`. ```go - fsMetaPath := pathJoin(fs.fsPath, minioMetaBucket, bucketMetaPrefix, bucket, objectMetaPrefix, object, fsMetaJSONFile) + fsMetaPath := pathJoin(fs.fsPath, minioMetaBucket, bucketMetaPrefix, bucket, object, fsMetaJSONFile) wlk, err := fs.rwPool.Create(fsMetaPath) if err != nil { return ObjectInfo{}, toObjectErr(err, bucket, object) diff --git a/pkg/lock/lock.go b/pkg/lock/lock.go index f8632a667..1672ef762 100644 --- a/pkg/lock/lock.go +++ b/pkg/lock/lock.go @@ -19,10 +19,16 @@ package lock import ( + "errors" "os" "sync" ) +var ( + // ErrAlreadyLocked is returned if the underlying fd is already locked. + ErrAlreadyLocked = errors.New("file already locked") +) + // RLockedFile represents a read locked file, implements a special // closer which only closes the associated *os.File when the ref count. // has reached zero, i.e when all the readers have given up their locks. diff --git a/pkg/lock/lock_nix.go b/pkg/lock/lock_nix.go index 537255c01..703073f74 100644 --- a/pkg/lock/lock_nix.go +++ b/pkg/lock/lock_nix.go @@ -24,16 +24,12 @@ import ( "syscall" ) -// LockedOpenFile - initializes a new lock and protects -// the file from concurrent access across mount points. -// This implementation doesn't support all the open -// flags and shouldn't be considered as replacement -// for os.OpenFile(). -func LockedOpenFile(path string, flag int, perm os.FileMode) (*LockedFile, error) { - var lockType int +// Internal function implements support for both +// blocking and non blocking lock type. +func lockedOpenFile(path string, flag int, perm os.FileMode, lockType int) (*LockedFile, error) { switch flag { case syscall.O_RDONLY: - lockType = syscall.LOCK_SH + lockType |= syscall.LOCK_SH case syscall.O_WRONLY: fallthrough case syscall.O_RDWR: @@ -41,7 +37,7 @@ func LockedOpenFile(path string, flag int, perm os.FileMode) (*LockedFile, error case syscall.O_WRONLY | syscall.O_CREAT: fallthrough case syscall.O_RDWR | syscall.O_CREAT: - lockType = syscall.LOCK_EX + lockType |= syscall.LOCK_EX default: return nil, fmt.Errorf("Unsupported flag (%d)", flag) } @@ -53,6 +49,9 @@ func LockedOpenFile(path string, flag int, perm os.FileMode) (*LockedFile, error if err = syscall.Flock(int(f.Fd()), lockType); err != nil { f.Close() + if err == syscall.EWOULDBLOCK { + err = ErrAlreadyLocked + } return nil, err } @@ -73,3 +72,21 @@ func LockedOpenFile(path string, flag int, perm os.FileMode) (*LockedFile, error return &LockedFile{File: f}, nil } + +// TryLockedOpenFile - tries a new write lock, functionality +// it is similar to LockedOpenFile with with syscall.LOCK_EX +// mode but along with syscall.LOCK_NB such that the function +// doesn't wait forever but instead returns if it cannot +// acquire a write lock. +func TryLockedOpenFile(path string, flag int, perm os.FileMode) (*LockedFile, error) { + return lockedOpenFile(path, flag, perm, syscall.LOCK_NB) +} + +// LockedOpenFile - initializes a new lock and protects +// the file from concurrent access across mount points. +// This implementation doesn't support all the open +// flags and shouldn't be considered as replacement +// for os.OpenFile(). +func LockedOpenFile(path string, flag int, perm os.FileMode) (*LockedFile, error) { + return lockedOpenFile(path, flag, perm, 0) +} diff --git a/pkg/lock/lock_solaris.go b/pkg/lock/lock_solaris.go index 76aa769b6..d4a4fbe42 100644 --- a/pkg/lock/lock_solaris.go +++ b/pkg/lock/lock_solaris.go @@ -24,17 +24,8 @@ import ( "syscall" ) -// LockedOpenFile - initializes a new lock and protects -// the file from concurrent access across mount points. -// This implementation doesn't support all the open -// flags and shouldn't be considered as replacement -// for os.OpenFile(). -func LockedOpenFile(path string, flag int, perm os.FileMode) (*LockedFile, error) { - var lock syscall.Flock_t - lock.Start = 0 - lock.Len = 0 - lock.Pid = 0 - +// lockedOpenFile is an internal function. +func lockedOpenFile(path string, flag int, perm os.FileMode, rlockType int) (*LockedFile, error) { var lockType int16 switch flag { case syscall.O_RDONLY: @@ -51,16 +42,24 @@ func LockedOpenFile(path string, flag int, perm os.FileMode) (*LockedFile, error return nil, fmt.Errorf("Unsupported flag (%d)", flag) } - lock.Type = lockType - lock.Whence = 0 + var lock = syscall.Flock_t{ + Start: 0, + Len: 0, + Pid: 0, + Type: lockType, + Whence: 0, + } f, err := os.OpenFile(path, flag, perm) if err != nil { return nil, err } - if err = syscall.FcntlFlock(f.Fd(), syscall.F_SETLKW, &lock); err != nil { + if err = syscall.FcntlFlock(f.Fd(), rlockType, &lock); err != nil { f.Close() + if err == syscall.EAGAIN { + err = ErrLocked + } return nil, err } @@ -81,3 +80,21 @@ func LockedOpenFile(path string, flag int, perm os.FileMode) (*LockedFile, error return &LockedFile{f}, nil } + +// TryLockedOpenFile - tries a new write lock, functionality +// it is similar to LockedOpenFile with with syscall.LOCK_EX +// mode but along with syscall.LOCK_NB such that the function +// doesn't wait forever but instead returns if it cannot +// acquire a write lock. +func TryLockedOpenFile(path string, flag int, perm os.FileMode) (*LockedFile, error) { + return lockedOpenFile(path, flag, perm, syscall.F_SETLK) +} + +// LockedOpenFile - initializes a new lock and protects +// the file from concurrent access across mount points. +// This implementation doesn't support all the open +// flags and shouldn't be considered as replacement +// for os.OpenFile(). +func LockedOpenFile(path string, flag int, perm os.FileMode) (*LockedFile, error) { + return lockedOpenFile(path, flag, perm, syscall.F_SETLKW) +} diff --git a/pkg/lock/lock_windows.go b/pkg/lock/lock_windows.go index 258c66b3f..1a5fc44fe 100644 --- a/pkg/lock/lock_windows.go +++ b/pkg/lock/lock_windows.go @@ -19,7 +19,6 @@ package lock import ( - "errors" "fmt" "os" "syscall" @@ -31,24 +30,25 @@ import ( var ( modkernel32 = syscall.NewLazyDLL("kernel32.dll") procLockFileEx = modkernel32.NewProc("LockFileEx") - - errLocked = errors.New("The process cannot access the file because another process has locked a portion of the file.") ) const ( + // https://msdn.microsoft.com/en-us/library/windows/desktop/aa365203(v=vs.85).aspx + lockFileExclusiveLock = 2 + lockFileFailImmediately = 1 + // see https://msdn.microsoft.com/en-us/library/windows/desktop/ms681382(v=vs.85).aspx errLockViolation syscall.Errno = 0x21 ) -// LockedOpenFile - initializes a new lock and protects -// the file from concurrent access. -func LockedOpenFile(path string, flag int, perm os.FileMode) (*LockedFile, error) { +// lockedOpenFile is an internal function. +func lockedOpenFile(path string, flag int, perm os.FileMode, lockType uint32) (*LockedFile, error) { f, err := open(path, flag, perm) if err != nil { return nil, err } - if err = lockFile(syscall.Handle(f.Fd()), 0); err != nil { + if err = lockFile(syscall.Handle(f.Fd()), lockType); err != nil { f.Close() return nil, err } @@ -71,6 +71,21 @@ func LockedOpenFile(path string, flag int, perm os.FileMode) (*LockedFile, error return &LockedFile{File: f}, nil } +// TryLockedOpenFile - tries a new write lock, functionality +// it is similar to LockedOpenFile with with syscall.LOCK_EX +// mode but along with syscall.LOCK_NB such that the function +// doesn't wait forever but instead returns if it cannot +// acquire a write lock. +func TryLockedOpenFile(path string, flag int, perm os.FileMode) (*LockedFile, error) { + return lockedOpenFile(path, flag, perm, lockFileFailImmediately) +} + +// LockedOpenFile - initializes a new lock and protects +// the file from concurrent access. +func LockedOpenFile(path string, flag int, perm os.FileMode) (*LockedFile, error) { + return lockedOpenFile(path, flag, perm, 0) +} + // perm param is ignored, on windows file perms/NT acls // are not octet combinations. Providing access to NT // acls is out of scope here. @@ -121,7 +136,7 @@ func open(path string, flag int, perm os.FileMode) (*os.File, error) { func lockFile(fd syscall.Handle, flags uint32) error { // https://msdn.microsoft.com/en-us/library/windows/desktop/aa365203(v=vs.85).aspx - var flag uint32 = 2 // Lockfile exlusive. + var flag uint32 = lockFileExclusiveLock // Lockfile exlusive. flag |= flags if fd == syscall.InvalidHandle { @@ -131,8 +146,8 @@ func lockFile(fd syscall.Handle, flags uint32) error { err := lockFileEx(fd, flag, 1, 0, &syscall.Overlapped{}) if err == nil { return nil - } else if err.Error() == errLocked.Error() { - return errors.New("lock already acquired") + } else if err.Error() == "The process cannot access the file because another process has locked a portion of the file." { + return ErrAlreadyLocked } else if err != errLockViolation { return err } From c8947af227546d29cb07ad436f6eb9afd176d69d Mon Sep 17 00:00:00 2001 From: Nitish Tiwari Date: Tue, 13 Jun 2017 01:37:14 -0700 Subject: [PATCH 77/80] Update Kubernetes-yaml deployment example and Helm deployment doc with Minio image update steps (#4515) --- docs/orchestration/kubernetes-yaml/README.md | 75 +++++++++++++++----- docs/orchestration/kubernetes/README.md | 16 ++++- 2 files changed, 71 insertions(+), 20 deletions(-) diff --git a/docs/orchestration/kubernetes-yaml/README.md b/docs/orchestration/kubernetes-yaml/README.md index 20451a2f7..db7789884 100644 --- a/docs/orchestration/kubernetes-yaml/README.md +++ b/docs/orchestration/kubernetes-yaml/README.md @@ -4,17 +4,20 @@ - [Prerequisites](#prerequisites) - [Minio Standalone Server Deployment](#minio-standalone-server-deployment) - - [Standalone Quickstart](#standalone-quickstart) - - [Step 1: Create Persistent Volume Claim](#step-1-create-persistent-volume-claim) - - [Step 2: Create Deployment](#step-2-create-minio-deployment) - - [Step 3: Create LoadBalancer Service](#step-3-create-minio-service) - - [Step 4: Resource cleanup](#step-4-resource-cleanup) + - [Standalone Quickstart](#standalone-quickstart) + - [Create Persistent Volume Claim](#create-persistent-volume-claim) + - [Create Deployment](#create-minio-deployment) + - [Create LoadBalancer Service](#create-minio-service) + - [Update existing Minio Deployment](#update-existing-minio-deployment) + - [Resource cleanup](#standalone-resource-cleanup) + - [Minio Distributed Server Deployment](#minio-distributed-server-deployment) - - [Distributed Quickstart](#distributed-quickstart) - - [Step 1: Create Minio Headless Service](#step-1-create-minio-headless-service) - - [Step 2: Create Minio Statefulset](#step-2-create-minio-statefulset) - - [Step 3: Create LoadBalancer Service](#step-3-create-minio-service) - - [Step 4: Resource cleanup](#step-4-resource-cleanup) + - [Distributed Quickstart](#distributed-quickstart) + - [Create Minio Headless Service](#create-minio-headless-service) + - [Create Minio Statefulset](#create-minio-statefulset) + - [Create LoadBalancer Service](#create-minio-service) + - [Update existing Minio StatefulSet](#update-existing-minio-statefulset) + - [Resource cleanup](#distributed-resource-cleanup) ## Prerequisites @@ -42,7 +45,7 @@ kubectl create -f https://github.com/minio/minio/blob/master/docs/orchestration/ kubectl create -f https://github.com/minio/minio/blob/master/docs/orchestration/kubernetes-yaml/minio-standalone-service.yaml?raw=true ``` -### Step 1: Create Persistent Volume Claim +### Create Persistent Volume Claim Minio needs persistent storage to store objects. If there is no persistent storage, the data stored in Minio instance will be stored in the container file system and will be wiped off as soon as the container restarts. @@ -78,7 +81,7 @@ kubectl create -f https://github.com/minio/minio/blob/master/docs/orchestration/ persistentvolumeclaim "minio-pv-claim" created ``` -### Step 2: Create Minio Deployment +### Create Minio Deployment A deployment encapsulates replica sets and pods — so, if a pod goes down, replication controller makes sure another pod comes up automatically. This way you won’t need to bother about pod failures and will have a stable Minio service available. @@ -134,7 +137,7 @@ kubectl create -f https://github.com/minio/minio/blob/master/docs/orchestration/ deployment "minio-deployment" created ``` -### Step 3: Create Minio Service +### Create Minio Service Now that you have a Minio deployment running, you may either want to access it internally (within the cluster) or expose it as a Service onto an external (outside of your cluster, maybe public internet) IP address, depending on your use case. You can achieve this using Services. There are 3 major service types — default type is ClusterIP, which exposes a service to connection from inside the cluster. NodePort and LoadBalancer are two types that expose services to external traffic. @@ -169,9 +172,24 @@ NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE minio-service 10.55.248.23 104.199.249.165 9000:31852/TCP 1m ``` -### Step 4: Resource cleanup +### Update existing Minio Deployment + +You can update an existing Minio deployment to use a newer Minio release. To do this, use the `kubectl set image` command: + +```sh +kubectl set image deployment/minio-deployment minio= +``` + +Kubernetes will restart the deployment to update the image. You will get a message as shown below, on successful update: + +``` +deployment "minio-deployment" image updated +``` + +### Standalone Resource cleanup + +You can cleanup the cluster using -Once you are done, cleanup the cluster using ```sh kubectl delete deployment minio-deployment \ && kubectl delete pvc minio-pv-claim \ @@ -198,7 +216,7 @@ kubectl create -f https://github.com/minio/minio/blob/master/docs/orchestration/ kubectl create -f https://github.com/minio/minio/blob/master/docs/orchestration/kubernetes-yaml/minio-distributed-service.yaml?raw=true ``` -### Step 1: Create Minio Headless Service +### Create Minio Headless Service Headless Service controls the domain within which StatefulSets are created. The domain managed by this Service takes the form: `$(service name).$(namespace).svc.cluster.local` (where “cluster.local” is the cluster domain), and the pods in this domain take the form: `$(pod-name-{i}).$(service name).$(namespace).svc.cluster.local`. This is required to get a DNS resolvable URL for each of the pods created within the Statefulset. @@ -227,7 +245,7 @@ $ kubectl create -f https://github.com/minio/minio/blob/master/docs/orchestratio service "minio" created ``` -### Step 2: Create Minio Statefulset +### Create Minio Statefulset A StatefulSet provides a deterministic name and a unique identity to each pod, making it easy to deploy stateful distributed applications. To launch distributed Minio you 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 pods. StatefulSets offer a perfect way to handle this requirement. @@ -292,7 +310,7 @@ $ kubectl create -f https://github.com/minio/minio/blob/master/docs/orchestratio statefulset "minio" created ``` -### Step 3: Create Minio Service +### Create Minio Service Now that you have a Minio statefulset running, you may either want to access it internally (within the cluster) or expose it as a Service onto an external (outside of your cluster, maybe public internet) IP address, depending on your use case. You can achieve this using Services. There are 3 major service types — default type is ClusterIP, which exposes a service to connection from inside the cluster. NodePort and LoadBalancer are two types that expose services to external traffic. @@ -327,7 +345,26 @@ NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE minio-service 10.55.248.23 104.199.249.165 9000:31852/TCP 1m ``` -### Step 4: Resource cleanup +### Update existing Minio StatefulSet +You can update an existing Minio StatefulSet to use a newer Minio release. To do this, use the `kubectl patch statefulset` command: + +```sh +kubectl patch statefulset minio --type='json' -p='[{"op": "replace", "path": "/spec/template/spec/containers/0/image", "value":""}]' +``` + +On successful update, you should see the output below + +``` +statefulset "minio" patched +``` + +Then delete all the pods in your StatefulSet one by one as shown below. Kubernetes will restart those pods for you, using the new image. + +```sh +kubectl delete minio-0 +``` + +### Resource cleanup You can cleanup the cluster using ```sh diff --git a/docs/orchestration/kubernetes/README.md b/docs/orchestration/kubernetes/README.md index 0413cd2d5..35bb07b70 100644 --- a/docs/orchestration/kubernetes/README.md +++ b/docs/orchestration/kubernetes/README.md @@ -109,7 +109,21 @@ $ 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 +## 3. Updating Minio Release using Helm + +You can update an existing Minio Helm Release to use a newer Minio Docker image. To do this, use the `helm upgrade` command: + +```bash +$ helm upgrade --set imageTag= stable/minio +``` + +On successful update, you should see the output below + +```bash +Release "your-helm-release" has been upgraded. Happy Helming! +``` + +## 4. Uninstalling the Chart Assuming your release is named as `my-release`, delete it using the command: From 353f2d3a6e30f595a721abcd9cf1b108b041ee21 Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Tue, 13 Jun 2017 08:29:07 -0700 Subject: [PATCH 78/80] fs: Hold `format.json` readLock ref to avoid GC. (#4532) Looks like if we follow pattern such as ``` _ = rlk ``` Go can potentially kick in GC and close the fd when the reference is lost, only speculation is that the cause here is `SetFinalizer` which is set on `os.close()` internally in `os` stdlib. This is unexpected and unsual endeavour for Go, but we have to make sure the reference is never lost and always dies with the server. Fixes #4530 --- cmd/fs-v1-metadata.go | 48 +++++++++++++++------------------------- cmd/fs-v1.go | 18 +++++++++++---- pkg/lock/lock_solaris.go | 2 +- 3 files changed, 33 insertions(+), 35 deletions(-) diff --git a/cmd/fs-v1-metadata.go b/cmd/fs-v1-metadata.go index 29c595ed2..935783a78 100644 --- a/cmd/fs-v1-metadata.go +++ b/cmd/fs-v1-metadata.go @@ -290,17 +290,23 @@ func checkLockedValidFormatFS(fsPath string) (*lock.RLockedFile, error) { return rlk, traceError(err) } -// Writes the new format.json if unformatted, -// otherwise closes the input locked file -// and returns any error. -func writeFormatFS(lk *lock.LockedFile) error { +// Creates a new format.json if unformatted. +func createFormatFS(fsPath string) error { + fsFormatPath := pathJoin(fsPath, minioMetaBucket, formatConfigFile) + + // Attempt a write lock on formatConfigFile `format.json` + // file stored in minioMetaBucket(.minio.sys) directory. + lk, err := lock.TryLockedOpenFile(preparePath(fsFormatPath), os.O_RDWR|os.O_CREATE, 0600) + if err != nil { + return traceError(err) + } // Close the locked file upon return. defer lk.Close() // Load format on disk, checks if we are unformatted // writes the new format.json var format = &formatConfigV1{} - err := format.LoadFormat(lk) + err = format.LoadFormat(lk) if errorCause(err) == errUnformattedDisk { _, err = newFSFormat().WriteTo(lk) return err @@ -308,20 +314,12 @@ func writeFormatFS(lk *lock.LockedFile) error { return err } -func initFormatFS(fsPath, fsUUID string) (err error) { - fsFormatPath := pathJoin(fsPath, minioMetaBucket, formatConfigFile) - - // Once the filesystem has initialized hold the read lock for - // the life time of the server. This is done to ensure that under - // shared backend mode for FS, remote servers do not migrate - // or cause changes on backend format. - +func initFormatFS(fsPath string) (rlk *lock.RLockedFile, err error) { // This loop validates format.json by holding a read lock and // proceeds if disk unformatted to hold non-blocking WriteLock // If for some reason non-blocking WriteLock fails and the error // is lock.ErrAlreadyLocked i.e some other process is holding a // lock we retry in the loop again. - var rlk *lock.RLockedFile for { // Validate the `format.json` for expected values. rlk, err = checkLockedValidFormatFS(fsPath) @@ -330,34 +328,24 @@ func initFormatFS(fsPath, fsUUID string) (err error) { // Holding a read lock ensures that any write lock operation // is blocked if attempted in-turn avoiding corruption on // the backend disk. - _ = rlk // Hold the lock on `format.json` until server dies. - return nil + return rlk, nil case errorCause(err) == errUnformattedDisk: - // Attempt a write lock on formatConfigFile `format.json` - // file stored in minioMetaBucket(.minio.sys) directory. - var lk *lock.LockedFile - lk, err = lock.TryLockedOpenFile(preparePath(fsFormatPath), os.O_RDWR|os.O_CREATE, 0600) - if err != nil { + if err = createFormatFS(fsPath); err != nil { // Existing write locks detected. - if err == lock.ErrAlreadyLocked { + if errorCause(err) == lock.ErrAlreadyLocked { // Lock already present, sleep and attempt again. time.Sleep(100 * time.Millisecond) continue } // Unexpected error, return. - return traceError(err) + return nil, err } - // Write new format. - if err = writeFormatFS(lk); err != nil { - return err - } - // Loop will continue to attempt a - // read-lock on `format.json` . + // Loop will continue to attempt a read-lock on `format.json`. default: // Unhandled error return. - return err + return nil, err } } } diff --git a/cmd/fs-v1.go b/cmd/fs-v1.go index 6f9d0a9f1..c8d0db1d6 100644 --- a/cmd/fs-v1.go +++ b/cmd/fs-v1.go @@ -42,6 +42,9 @@ type fsObjects struct { // temporary transactions. fsUUID string + // This value shouldn't be touched, once initialized. + fsFormatRlk *lock.RLockedFile // Is a read lock on `format.json`. + // FS rw pool. rwPool *fsIOPool @@ -109,6 +112,12 @@ func newFSObjectLayer(fsPath string) (ObjectLayer, error) { return nil, fmt.Errorf("Unable to initialize '.minio.sys' meta volume, %s", err) } + // Initialize `format.json`, this function also returns. + rlk, err := initFormatFS(fsPath) + if err != nil { + return nil, err + } + // Initialize fs objects. fs := &fsObjects{ fsPath: fsPath, @@ -122,10 +131,11 @@ func newFSObjectLayer(fsPath string) (ObjectLayer, error) { }, } - // Initialize `format.json`. - if err = initFormatFS(fsPath, fsUUID); err != nil { - return nil, err - } + // Once the filesystem has initialized hold the read lock for + // the life time of the server. This is done to ensure that under + // shared backend mode for FS, remote servers do not migrate + // or cause changes on backend format. + fs.fsFormatRlk = rlk // Initialize and load bucket policies. err = initBucketPolicies(fs) diff --git a/pkg/lock/lock_solaris.go b/pkg/lock/lock_solaris.go index d4a4fbe42..ba1f61816 100644 --- a/pkg/lock/lock_solaris.go +++ b/pkg/lock/lock_solaris.go @@ -58,7 +58,7 @@ func lockedOpenFile(path string, flag int, perm os.FileMode, rlockType int) (*Lo if err = syscall.FcntlFlock(f.Fd(), rlockType, &lock); err != nil { f.Close() if err == syscall.EAGAIN { - err = ErrLocked + err = ErrAlreadyLocked } return nil, err } From 26903da8c2135bf952ac1d772a9a4b63e82ea15b Mon Sep 17 00:00:00 2001 From: Nitish Tiwari Date: Tue, 13 Jun 2017 16:10:11 -0700 Subject: [PATCH 79/80] Add steps to remove Minio volumes in the swarm (#4536) --- docs/orchestration/docker-swarm/README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/orchestration/docker-swarm/README.md b/docs/orchestration/docker-swarm/README.md index 2e226b2f3..e8bbe71eb 100644 --- a/docs/orchestration/docker-swarm/README.md +++ b/docs/orchestration/docker-swarm/README.md @@ -51,6 +51,16 @@ Remove the distributed Minio services and related network by ```shell docker stack rm minio_stack ``` +Swarm doesn't automatically remove host volumes created for services. This may lead to corruption when a new Minio service is created in the swarm. So, we recommend removing all the volumes used by Minio, manually. To do this, go to each node in the swarm and list the volumes by + +```shell +docker volume ls +``` +Then remove `minio_stack` volumes by + +```shell +docker volume rm volume_name +``` ### Notes From 11c4223f2c308afa436e6734b65802b4e5c7c21b Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Wed, 14 Jun 2017 02:08:35 -0700 Subject: [PATCH 80/80] Add docker release files. (#4473) We used to release by building directly on the docker hub auto build process, which is sufficient for edge but it is not a good idea to do it for stable releases. Do not build docker release binaries again, but instead use the released binaries themselves which are signed and validated. --- Dockerfile | 2 +- Dockerfile.release | 23 +++++++++++++++++++++++ Dockerfile.release.aarch64 | 22 ++++++++++++++++++++++ Dockerfile.release.armhf | 22 ++++++++++++++++++++++ 4 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 Dockerfile.release create mode 100644 Dockerfile.release.aarch64 create mode 100644 Dockerfile.release.armhf diff --git a/Dockerfile b/Dockerfile index a3150ba1c..6e193a5e5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,7 +11,7 @@ WORKDIR /go/src/github.com/minio/ RUN \ apk add --no-cache ca-certificates && \ apk add --no-cache --virtual .build-deps git go musl-dev && \ - echo 'hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4' >> /etc/nsswitch.conf && \ + echo 'hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4' >> /etc/nsswitch.conf && \ 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)" && \ diff --git a/Dockerfile.release b/Dockerfile.release new file mode 100644 index 000000000..10272aa63 --- /dev/null +++ b/Dockerfile.release @@ -0,0 +1,23 @@ +FROM alpine:3.5 + +MAINTAINER Minio Inc + +RUN \ + apk add --no-cache ca-certificates && \ + apk add --no-cache --virtual .build-deps curl && \ + echo 'hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4' >> /etc/nsswitch.conf && \ + curl https://dl.minio.io/server/minio/release/linux-amd64/minio > /usr/bin/minio && \ + chmod +x /usr/bin/minio && apk del .build-deps + +EXPOSE 9000 + +COPY buildscripts/docker-entrypoint.sh /usr/bin/ + +RUN chmod +x /usr/bin/docker-entrypoint.sh + +ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"] + +VOLUME ["/export"] + +CMD ["minio"] + diff --git a/Dockerfile.release.aarch64 b/Dockerfile.release.aarch64 new file mode 100644 index 000000000..072b4f858 --- /dev/null +++ b/Dockerfile.release.aarch64 @@ -0,0 +1,22 @@ +FROM resin/aarch64-alpine:3.5 + +MAINTAINER Minio Inc + +RUN \ + apk add --no-cache ca-certificates && \ + apk add --no-cache --virtual .build-deps curl && \ + echo 'hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4' >> /etc/nsswitch.conf && \ + curl https://dl.minio.io/server/minio/release/linux-arm64/minio > /usr/bin/minio && \ + chmod +x /usr/bin/minio && apk del .build-deps + +EXPOSE 9000 + +COPY buildscripts/docker-entrypoint.sh /usr/bin/ + +RUN chmod +x /usr/bin/docker-entrypoint.sh + +ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"] + +VOLUME ["/export"] + +CMD ["minio"] diff --git a/Dockerfile.release.armhf b/Dockerfile.release.armhf new file mode 100644 index 000000000..b21ef6b58 --- /dev/null +++ b/Dockerfile.release.armhf @@ -0,0 +1,22 @@ +FROM resin/armhf-alpine:3.5 + +MAINTAINER Minio Inc + +RUN \ + apk add --no-cache ca-certificates && \ + apk add --no-cache --virtual .build-deps curl && \ + echo 'hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4' >> /etc/nsswitch.conf && \ + curl https://dl.minio.io/server/minio/release/linux-arm/minio > /usr/bin/minio && \ + chmod +x /usr/bin/minio && apk del .build-deps + +EXPOSE 9000 + +COPY buildscripts/docker-entrypoint.sh /usr/bin/ + +RUN chmod +x /usr/bin/docker-entrypoint.sh + +ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"] + +VOLUME ["/export"] + +CMD ["minio"]