Add avatar max size limit
authorChocobozzz <me@florianbigard.com>
Wed, 3 Jan 2018 10:10:40 +0000 (11:10 +0100)
committerChocobozzz <me@florianbigard.com>
Wed, 3 Jan 2018 10:10:40 +0000 (11:10 +0100)
17 files changed:
client/src/app/account/account-settings/account-settings.component.html
client/src/app/account/account-settings/account-settings.component.scss
client/src/app/account/account-settings/account-settings.component.ts
client/src/app/core/server/server.service.ts
package.json
server.ts
server/controllers/api/config.ts
server/helpers/custom-validators/activitypub/actor.ts
server/helpers/custom-validators/activitypub/misc.ts
server/initializers/constants.ts
server/middlewares/validators/users.ts
server/models/activitypub/actor.ts
server/models/server/server.ts
server/tests/api/check-params/users.ts
server/tests/api/fixtures/avatar-big.png [new file with mode: 0644]
shared/models/server-config.model.ts
yarn.lock

index fe345207a668305c9b4ca2a9c321724dabb6e345..0d1637c4088509ed09862026d7b3d41f0fca6258 100644 (file)
@@ -9,8 +9,9 @@
 
 <div class="button-file">
   <span>Change your avatar</span>
-  <input #avatarfileInput type="file" name="avatarfile" id="avatarfile" (change)="changeAvatar()" />
+  <input #avatarfileInput type="file" name="avatarfile" id="avatarfile" [accept]="avatarExtensions" (change)="changeAvatar()" />
 </div>
+<div class="file-max-size">(extensions: {{ avatarExtensions }}, max size: {{ maxAvatarSize | bytes }})</div>
 
 <div class="account-title">Account settings</div>
 <my-account-change-password></my-account-change-password>
index accd65214ff13c82447d7358451bdafa5c94e926..c8a27dbcd060b7bf39cfc1f85eb5b2e9db54ec44 100644 (file)
 }
 
 .button-file {
-  @include peertube-button-file(auto);
+  @include peertube-button-file(160px);
 
   margin-top: 10px;
 }
 
+.file-max-size {
+  display: inline-block;
+  font-size: 13px;
+
+  position: relative;
+  top: -10px;
+}
+
 .account-title {
   text-transform: uppercase;
   color: $orange-color;
index 3e03085cedad88c5a2923356ae19edcae1c43e8d..d5f5ff30f6df24184c45f99223026e882b2db0e5 100644 (file)
@@ -1,9 +1,8 @@
-import { HttpEventType, HttpResponse } from '@angular/common/http'
 import { Component, OnInit, ViewChild } from '@angular/core'
 import { NotificationsService } from 'angular2-notifications'
-import { VideoPrivacy } from '../../../../../shared/models/videos'
-import { User } from '../../shared'
 import { AuthService } from '../../core'
+import { ServerService } from '../../core/server'
+import { User } from '../../shared'
 import { UserService } from '../../shared/users'
 
 @Component({
@@ -19,6 +18,7 @@ export class AccountSettingsComponent implements OnInit {
   constructor (
     private userService: UserService,
     private authService: AuthService,
+    private serverService: ServerService,
     private notificationsService: NotificationsService
   ) {}
 
@@ -47,4 +47,12 @@ export class AccountSettingsComponent implements OnInit {
         err => this.notificationsService.error('Error', err.message)
       )
   }
+
+  get maxAvatarSize () {
+    return this.serverService.getConfig().avatar.file.size.max
+  }
+
+  get avatarExtensions () {
+    return this.serverService.getConfig().avatar.file.extensions.join(',')
+  }
 }
index a5be9e19958406fed4edb3972ba914b479262890..45f68b4340731abd2918ee0c7efaee36ca6fd5bf 100644 (file)
@@ -21,6 +21,17 @@ export class ServerService {
     },
     transcoding: {
       enabledResolutions: []
+    },
+    avatar: {
+      file: {
+        size: { max: 0 },
+        extensions: []
+      }
+    },
+    video: {
+      file: {
+        extensions: []
+      }
     }
   }
   private videoCategories: Array<{ id: number, label: string }> = []
index f6e10d7096210a20000134f14575fc66d251a16b..adaccaf37f769bc414bad731c68ad09965c6fe7d 100644 (file)
@@ -86,6 +86,7 @@
     "scripty": "^1.5.0",
     "sequelize": "4.25.2",
     "sequelize-typescript": "^0.6.1",
+    "sharp": "^0.18.4",
     "ts-node": "^3.3.0",
     "typescript": "^2.5.2",
     "uuid": "^3.1.0",
index 05fc39acbb5a808cfeccf5e4384ce440ee912884..e46ff85c7c214200a3b03b8d2de884151284ee49 100644 (file)
--- a/server.ts
+++ b/server.ts
@@ -164,7 +164,7 @@ function onDatabaseInitDone () {
     .then(() => {
       // ----------- Make the server listening -----------
       server.listen(port, () => {
-        VideosPreviewCache.Instance.init(CONFIG.CACHE.PREVIEWS.SIZE)
+        VideosPreviewCache.Instance.init(CONFIG.CACHE.PREVIEWS.FILE_SIZE)
         activitypubHttpJobScheduler.activate()
         transcodingJobScheduler.activate()
 
index 2f1132904fef5846bf54e8a93c75a096d7115513..35c89835b3d613f80f08f330c50012f2ab9411e1 100644 (file)
@@ -1,7 +1,7 @@
 import * as express from 'express'
 import { isSignupAllowed } from '../../helpers/utils'
 
-import { CONFIG } from '../../initializers'
+import { CONFIG, CONSTRAINTS_FIELDS } from '../../initializers'
 import { asyncMiddleware } from '../../middlewares'
 import { ServerConfig } from '../../../shared'
 
@@ -24,6 +24,19 @@ async function getConfig (req: express.Request, res: express.Response, next: exp
     },
     transcoding: {
       enabledResolutions
+    },
+    avatar: {
+      file: {
+        size: {
+          max: CONSTRAINTS_FIELDS.ACTORS.AVATAR.FILE_SIZE.max
+        },
+        extensions: CONSTRAINTS_FIELDS.ACTORS.AVATAR.EXTNAME
+      }
+    },
+    video: {
+      file: {
+        extensions: CONSTRAINTS_FIELDS.VIDEOS.EXTNAME
+      }
     }
   }
 
index 630bace3098c0ffb156a06f0d34dd00477d33926..700e060070472984b6b4030109df41179db05672 100644 (file)
@@ -24,7 +24,7 @@ function isActorPublicKeyValid (publicKey: string) {
     typeof publicKey === 'string' &&
     publicKey.startsWith('-----BEGIN PUBLIC KEY-----') &&
     publicKey.indexOf('-----END PUBLIC KEY-----') !== -1 &&
-    validator.isLength(publicKey, CONSTRAINTS_FIELDS.ACTOR.PUBLIC_KEY)
+    validator.isLength(publicKey, CONSTRAINTS_FIELDS.ACTORS.PUBLIC_KEY)
 }
 
 const actorNameRegExp = new RegExp('[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_]+')
@@ -42,7 +42,7 @@ function isActorPrivateKeyValid (privateKey: string) {
     privateKey.startsWith('-----BEGIN RSA PRIVATE KEY-----') &&
     // Sometimes there is a \n at the end, so just assert the string contains the end mark
     privateKey.indexOf('-----END RSA PRIVATE KEY-----') !== -1 &&
-    validator.isLength(privateKey, CONSTRAINTS_FIELDS.ACTOR.PRIVATE_KEY)
+    validator.isLength(privateKey, CONSTRAINTS_FIELDS.ACTORS.PRIVATE_KEY)
 }
 
 function isRemoteActorValid (remoteActor: any) {
index aa6ffc9bddfeaefd73fb9ec0202f9359f13a0835..75d308e9d37972a1bdda5b6eaa223d4aad8728ad 100644 (file)
@@ -17,7 +17,7 @@ function isActivityPubUrlValid (url: string) {
     isURLOptions.require_tld = false
   }
 
-  return exists(url) && validator.isURL('' + url, isURLOptions) && validator.isLength('' + url, CONSTRAINTS_FIELDS.ACTOR.URL)
+  return exists(url) && validator.isURL('' + url, isURLOptions) && validator.isLength('' + url, CONSTRAINTS_FIELDS.ACTORS.URL)
 }
 
 function isBaseActivityValid (activity: any, type: string) {
index 31bb6c98139d97d87d2b4aa3b00da13628c1879b..aefb91537765b26c9024f91e813fce5bcb771bc5 100644 (file)
@@ -133,9 +133,6 @@ const CONFIG = {
   }
 }
 
-const AVATARS_DIR = {
-  ACCOUNT: join(CONFIG.STORAGE.AVATARS_DIR, 'account')
-}
 // ---------------------------------------------------------------------------
 
 const CONSTRAINTS_FIELDS = {
@@ -169,12 +166,15 @@ const CONSTRAINTS_FIELDS = {
     FILE_SIZE: { min: 10 },
     URL: { min: 3, max: 2000 } // Length
   },
-  ACTOR: {
+  ACTORS: {
     PUBLIC_KEY: { min: 10, max: 5000 }, // Length
     PRIVATE_KEY: { min: 10, max: 5000 }, // Length
     URL: { min: 3, max: 2000 }, // Length
     AVATAR: {
-      EXTNAME: [ '.png', '.jpeg', '.jpg' ]
+      EXTNAME: [ '.png', '.jpeg', '.jpg' ],
+      FILE_SIZE: {
+        max: 2 * 1024 * 1024 // 2MB
+      }
     }
   },
   VIDEO_EVENTS: {
@@ -345,6 +345,7 @@ if (isTestInstance() === true) {
   REMOTE_SCHEME.WS = 'ws'
   STATIC_MAX_AGE = '0'
   ACTIVITY_PUB.COLLECTION_ITEMS_PER_PAGE = 2
+  CONSTRAINTS_FIELDS.ACTORS.AVATAR.FILE_SIZE.max = 100 * 1024 // 100KB
 }
 
 CONFIG.WEBSERVER.URL = sanitizeUrl(CONFIG.WEBSERVER.SCHEME + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT)
@@ -372,7 +373,6 @@ export {
   PREVIEWS_SIZE,
   REMOTE_SCHEME,
   FOLLOW_STATES,
-  AVATARS_DIR,
   SERVER_ACTOR_NAME,
   PRIVATE_RSA_KEY_SIZE,
   SORTABLE_COLUMNS,
index 7c77e9a3945950a61bd57aca53b75f5f5db3f87d..7de3e442ccec31cbabe8ccc295adfc45982eabc7 100644 (file)
@@ -12,6 +12,7 @@ import { isSignupAllowed } from '../../helpers/utils'
 import { CONSTRAINTS_FIELDS } from '../../initializers'
 import { UserModel } from '../../models/account/user'
 import { areValidationErrors } from './utils'
+import Multer = require('multer')
 
 const usersAddValidator = [
   body('username').custom(isUserUsernameValid).withMessage('Should have a valid username (lowercase alphanumeric characters)'),
@@ -100,7 +101,7 @@ const usersUpdateMeValidator = [
 const usersUpdateMyAvatarValidator = [
   body('avatarfile').custom((value, { req }) => isAvatarFile(req.files)).withMessage(
     'This file is not supported. Please, make sure it is of the following type : '
-    + CONSTRAINTS_FIELDS.ACTOR.AVATAR.EXTNAME.join(', ')
+    + CONSTRAINTS_FIELDS.ACTORS.AVATAR.EXTNAME.join(', ')
   ),
 
   (req: express.Request, res: express.Response, next: express.NextFunction) => {
@@ -108,6 +109,14 @@ const usersUpdateMyAvatarValidator = [
 
     if (areValidationErrors(req, res)) return
 
+    const imageFile = req.files['avatarfile'][0] as Express.Multer.File
+    if (imageFile.size > CONSTRAINTS_FIELDS.ACTORS.AVATAR.FILE_SIZE.max) {
+      res.status(400)
+        .send({ error: `The size of the avatar is too big (>${CONSTRAINTS_FIELDS.ACTORS.AVATAR.FILE_SIZE.max}).` })
+        .end()
+      return
+    }
+
     return next()
   }
 ]
index a12f3ec9eb4ece1c1caab419298c9dcaf5c26ebe..ff5ab2e327a9fcdc1c256cb9715789b0a8397337 100644 (file)
@@ -87,17 +87,17 @@ export class ActorModel extends Model<ActorModel> {
 
   @AllowNull(false)
   @Is('ActorUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'url'))
-  @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTOR.URL.max))
+  @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max))
   url: string
 
   @AllowNull(true)
   @Is('ActorPublicKey', value => throwIfNotValid(value, isActorPublicKeyValid, 'public key'))
-  @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTOR.PUBLIC_KEY.max))
+  @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.PUBLIC_KEY.max))
   publicKey: string
 
   @AllowNull(true)
   @Is('ActorPublicKey', value => throwIfNotValid(value, isActorPrivateKeyValid, 'private key'))
-  @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTOR.PRIVATE_KEY.max))
+  @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.PRIVATE_KEY.max))
   privateKey: string
 
   @AllowNull(false)
@@ -112,27 +112,27 @@ export class ActorModel extends Model<ActorModel> {
 
   @AllowNull(false)
   @Is('ActorInboxUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'inbox url'))
-  @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTOR.URL.max))
+  @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max))
   inboxUrl: string
 
   @AllowNull(false)
   @Is('ActorOutboxUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'outbox url'))
-  @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTOR.URL.max))
+  @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max))
   outboxUrl: string
 
   @AllowNull(false)
   @Is('ActorSharedInboxUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'shared inbox url'))
-  @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTOR.URL.max))
+  @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max))
   sharedInboxUrl: string
 
   @AllowNull(false)
   @Is('ActorFollowersUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'followers url'))
-  @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTOR.URL.max))
+  @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max))
   followersUrl: string
 
   @AllowNull(false)
   @Is('ActorFollowingUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'following url'))
-  @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTOR.URL.max))
+  @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max))
   followingUrl: string
 
   @CreatedAt
index d35aa0ca402222f3ec6a2c54bdf371739867846f..122e5f74fcd5190bb38494bf387901e4ff37a533 100644 (file)
@@ -27,7 +27,7 @@ export class ServerModel extends Model<ServerModel> {
   @AllowNull(false)
   @Default(SERVERS_SCORE.BASE)
   @IsInt
-  @Max(SERVERS_SCORE.MAX)
+  @Max(SERVERS_SCORE.max)
   @Column
   score: number
 
index 33d92ac24b4b38b06cf7caef713e6735bf4329f7..14fcf870327689ab41371f37ffb8b7e01759ea5e 100644 (file)
@@ -276,6 +276,14 @@ describe('Test users API validators', function () {
       await makePostUploadRequest({ url: server.url, path: path + '/me/avatar/pick', token: server.accessToken, fields, attaches })
     })
 
+    it('Should fail with a big file', async function () {
+      const fields = {}
+      const attaches = {
+        'avatarfile': join(__dirname, '..', 'fixtures', 'avatar-big.png')
+      }
+      await makePostUploadRequest({ url: server.url, path: path + '/me/avatar/pick', token: server.accessToken, fields, attaches })
+    })
+
     it('Should succeed with the correct params', async function () {
       const fields = {}
       const attaches = {
diff --git a/server/tests/api/fixtures/avatar-big.png b/server/tests/api/fixtures/avatar-big.png
new file mode 100644 (file)
index 0000000..e593e40
Binary files /dev/null and b/server/tests/api/fixtures/avatar-big.png differ
index 8de808e60b2d8651a124d0f518a15525f3698555..d0b2e40de5c4f1965f2d0e429805078a94787a79 100644 (file)
@@ -5,4 +5,17 @@ export interface ServerConfig {
   transcoding: {
     enabledResolutions: number[]
   }
+  avatar: {
+    file: {
+      size: {
+        max: number
+      },
+      extensions: string[]
+    }
+  }
+  video: {
+    file: {
+      extensions: string[]
+    }
+  }
 }
index 101428df81217b71636c636de7f8a11bf7b7b166..67337c08bbc6f53c5018649f5b4286d1a166d8af 100644 (file)
--- a/yarn.lock
+++ b/yarn.lock
@@ -756,6 +756,15 @@ caseless@~0.12.0:
   version "0.12.0"
   resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
 
+caw@^2.0.0:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/caw/-/caw-2.0.1.tgz#6c3ca071fc194720883c2dc5da9b074bfc7e9e95"
+  dependencies:
+    get-proxy "^2.0.0"
+    isurl "^1.0.0-alpha5"
+    tunnel-agent "^0.6.0"
+    url-to-options "^1.0.1"
+
 chai@^4.1.1:
   version "4.1.2"
   resolved "https://registry.yarnpkg.com/chai/-/chai-4.1.2.tgz#0f64584ba642f0f2ace2806279f4f06ca23ad73c"
@@ -878,16 +887,30 @@ code-point-at@^1.0.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
 
-color-convert@^1.9.0:
+color-convert@^1.9.0, color-convert@^1.9.1:
   version "1.9.1"
   resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.1.tgz#c1261107aeb2f294ebffec9ed9ecad529a6097ed"
   dependencies:
     color-name "^1.1.1"
 
-color-name@^1.1.1:
+color-name@^1.0.0, color-name@^1.1.1:
   version "1.1.3"
   resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
 
+color-string@^1.5.2:
+  version "1.5.2"
+  resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.2.tgz#26e45814bc3c9a7cbd6751648a41434514a773a9"
+  dependencies:
+    color-name "^1.0.0"
+    simple-swizzle "^0.2.2"
+
+color@^2.0.0:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/color/-/color-2.0.1.tgz#e4ed78a3c4603d0891eba5430b04b86314f4c839"
+  dependencies:
+    color-convert "^1.9.1"
+    color-string "^1.5.2"
+
 colors@1.0.x:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b"
@@ -945,6 +968,13 @@ concurrently@^3.1.0:
     supports-color "^3.2.3"
     tree-kill "^1.1.0"
 
+config-chain@^1.1.11:
+  version "1.1.11"
+  resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.11.tgz#aba09747dfbe4c3e70e766a6e41586e1859fc6f2"
+  dependencies:
+    ini "^1.3.4"
+    proto-list "~1.2.1"
+
 config@^1.14.0:
   version "1.28.1"
   resolved "https://registry.yarnpkg.com/config/-/config-1.28.1.tgz#7625d2a1e4c90f131d8a73347982d93c3873282d"
@@ -1161,6 +1191,10 @@ destroy@~1.0.4:
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
 
+detect-libc@^0.2.0:
+  version "0.2.0"
+  resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-0.2.0.tgz#47fdf567348a17ec25fcbf0b9e446348a76f9fb5"
+
 detect-libc@^1.0.2:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b"
@@ -1887,6 +1921,12 @@ get-func-name@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41"
 
+get-proxy@^2.0.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/get-proxy/-/get-proxy-2.1.0.tgz#349f2b4d91d44c4d4d4e9cba2ad90143fac5ef93"
+  dependencies:
+    npm-conf "^1.1.0"
+
 get-stdin@^5.0.1:
   version "5.0.1"
   resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-5.0.1.tgz#122e161591e21ff4c52530305693f20e6393a398"
@@ -2016,6 +2056,16 @@ has-flag@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51"
 
+has-symbol-support-x@^1.4.1:
+  version "1.4.1"
+  resolved "https://registry.yarnpkg.com/has-symbol-support-x/-/has-symbol-support-x-1.4.1.tgz#66ec2e377e0c7d7ccedb07a3a84d77510ff1bc4c"
+
+has-to-string-tag-x@^1.2.0:
+  version "1.4.1"
+  resolved "https://registry.yarnpkg.com/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz#a045ab383d7b4b2012a00148ab0aa5f290044d4d"
+  dependencies:
+    has-symbol-support-x "^1.4.1"
+
 has-unicode@^2.0.0:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
@@ -2196,6 +2246,10 @@ is-arrayish@^0.2.1:
   version "0.2.1"
   resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
 
+is-arrayish@^0.3.1:
+  version "0.3.1"
+  resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.1.tgz#c2dfc386abaa0c3e33c48db3fe87059e69065efd"
+
 is-ascii@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/is-ascii/-/is-ascii-1.0.0.tgz#f02ad0259a0921cd199ff21ce1b09e0f6b4e3929"
@@ -2300,6 +2354,10 @@ is-obj@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f"
 
+is-object@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.1.tgz#8952688c5ec2ffd6b03ecc85e769e02903083470"
+
 is-path-cwd@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d"
@@ -2380,6 +2438,13 @@ isstream@0.1.x, isstream@~0.1.2:
   version "0.1.2"
   resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
 
+isurl@^1.0.0-alpha5:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/isurl/-/isurl-1.0.0.tgz#b27f4f49f3cdaa3ea44a0a5b7f3462e6edc39d67"
+  dependencies:
+    has-to-string-tag-x "^1.2.0"
+    is-object "^1.0.1"
+
 js-string-escape@1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/js-string-escape/-/js-string-escape-1.0.1.tgz#e2625badbc0d67c7533e9edc1068c587ae4137ef"
@@ -2779,6 +2844,18 @@ minimist@^1.1.0, minimist@^1.1.1, minimist@^1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
 
+minipass@^2.0.2, minipass@^2.2.1:
+  version "2.2.1"
+  resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.2.1.tgz#5ada97538b1027b4cf7213432428578cb564011f"
+  dependencies:
+    yallist "^3.0.0"
+
+minizlib@^1.0.3:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.1.0.tgz#11e13658ce46bc3a70a267aac58359d1e0c29ceb"
+  dependencies:
+    minipass "^2.2.1"
+
 mkdirp@0.5.1, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1:
   version "0.5.1"
   resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
@@ -2867,7 +2944,7 @@ nan@2.6.2:
   version "2.6.2"
   resolved "https://registry.yarnpkg.com/nan/-/nan-2.6.2.tgz#e4ff34e6c95fdfb5aecc08de6596f43605a7db45"
 
-nan@^2.3.0, nan@^2.7.0:
+nan@^2.3.0, nan@^2.6.2, nan@^2.7.0:
   version "2.8.0"
   resolved "https://registry.yarnpkg.com/nan/-/nan-2.8.0.tgz#ed715f3fe9de02b57a5e6252d90a96675e1f085a"
 
@@ -2969,6 +3046,13 @@ normalize-path@^2.0.0, normalize-path@^2.0.1:
   dependencies:
     remove-trailing-separator "^1.0.1"
 
+npm-conf@^1.1.0:
+  version "1.1.3"
+  resolved "https://registry.yarnpkg.com/npm-conf/-/npm-conf-1.1.3.tgz#256cc47bd0e218c259c4e9550bf413bc2192aff9"
+  dependencies:
+    config-chain "^1.1.11"
+    pify "^3.0.0"
+
 npm-run-path@^2.0.0:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f"
@@ -3399,6 +3483,10 @@ promisify-any@2.0.1:
     co-bluebird "^1.1.0"
     is-generator "^1.0.2"
 
+proto-list@~1.2.1:
+  version "1.2.4"
+  resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849"
+
 proxy-addr@~2.0.2:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.2.tgz#6571504f47bb988ec8180253f85dd7e14952bdec"
@@ -3828,6 +3916,18 @@ setprototypeof@1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656"
 
+sharp@^0.18.4:
+  version "0.18.4"
+  resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.18.4.tgz#fe329c0f06896c28aa24376df1fff02ae57f2d34"
+  dependencies:
+    caw "^2.0.0"
+    color "^2.0.0"
+    detect-libc "^0.2.0"
+    nan "^2.6.2"
+    semver "^5.3.0"
+    simple-get "^2.7.0"
+    tar "^3.1.5"
+
 shebang-command@^1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
@@ -3866,7 +3966,7 @@ simple-get@^1.4.2:
     unzip-response "^1.0.0"
     xtend "^4.0.0"
 
-simple-get@^2.0.0, simple-get@^2.2.1:
+simple-get@^2.0.0, simple-get@^2.2.1, simple-get@^2.7.0:
   version "2.7.0"
   resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-2.7.0.tgz#ad37f926d08129237ff08c4f2edfd6f10e0380b5"
   dependencies:
@@ -3890,6 +3990,12 @@ simple-sha1@^2.0.0, simple-sha1@^2.0.8, simple-sha1@^2.1.0:
   dependencies:
     rusha "^0.8.1"
 
+simple-swizzle@^0.2.2:
+  version "0.2.2"
+  resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a"
+  dependencies:
+    is-arrayish "^0.3.1"
+
 simple-websocket@^5.0.0:
   version "5.1.0"
   resolved "https://registry.yarnpkg.com/simple-websocket/-/simple-websocket-5.1.0.tgz#9fc2e4127f217ace4871f10c56bd21c85ecca21d"
@@ -4209,6 +4315,16 @@ tar@^2.2.1:
     fstream "^1.0.2"
     inherits "2"
 
+tar@^3.1.5:
+  version "3.2.1"
+  resolved "https://registry.yarnpkg.com/tar/-/tar-3.2.1.tgz#9aa8e41c88f09e76c166075bc71f93d5166e61b1"
+  dependencies:
+    chownr "^1.0.1"
+    minipass "^2.0.2"
+    minizlib "^1.0.3"
+    mkdirp "^0.5.0"
+    yallist "^3.0.2"
+
 term-size@^1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/term-size/-/term-size-1.2.0.tgz#458b83887f288fc56d6fffbfad262e26638efa69"
@@ -4459,6 +4575,10 @@ url-parse-lax@^1.0.0:
   dependencies:
     prepend-http "^1.0.1"
 
+url-to-options@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/url-to-options/-/url-to-options-1.0.1.tgz#1505a03a289a48cbd7a434efbaeec5055f5633a9"
+
 user-home@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/user-home/-/user-home-2.0.0.tgz#9c70bfd8169bc1dcbf48604e0f04b8b49cde9e9f"
@@ -4674,6 +4794,10 @@ yallist@^2.1.2:
   version "2.1.2"
   resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52"
 
+yallist@^3.0.0, yallist@^3.0.2:
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.2.tgz#8452b4bb7e83c7c188d8041c1a837c773d6d8bb9"
+
 yargs-parser@^8.0.0:
   version "8.0.0"
   resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-8.0.0.tgz#21d476330e5a82279a4b881345bf066102e219c6"