Compare commits
	
		
			34 Commits
		
	
	
		
			features/v
			...
			v2.6.4
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 13979a84f9 | ||
|  | 82570019ba | ||
|  | a1216e6315 | ||
|  | 34de90c486 | ||
|  | 442f335504 | ||
|  | 58108b4481 | ||
|  | cc0c1674f0 | ||
|  | 49f49cf367 | ||
|  | ec20a5d53a | ||
|  | 404dc97fb0 | ||
|  | a2cda74ba3 | ||
|  | 12bdd7dc5f | ||
|  | 15dcb414bf | ||
|  | 2c36d35784 | ||
|  | c0736c466c | ||
|  | fa02f878fc | ||
|  | ecc58c0f23 | ||
|  | 6d4438a6ae | ||
|  | 01a8ab921e | ||
|  | a3ef076160 | ||
|  | cd8575aef6 | ||
|  | 4ce6ed2021 | ||
|  | 886ef1cc38 | ||
|  | d06a724b1c | ||
|  | f73b7e77da | ||
|  | 63f168c3bf | ||
|  | 0f436de035 | ||
|  | 21fd335dd7 | ||
|  | 4b2f254806 | ||
|  | b3c29ece47 | ||
|  | 330401bec0 | ||
|  | 5ee4fd4606 | ||
|  | 430499fbe1 | ||
|  | 449e6e451f | 
							
								
								
									
										58
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										58
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -3,6 +3,64 @@ Changelog | ||||
|  | ||||
| All notable changes to this project will be documented in this file. | ||||
|  | ||||
| ## [2.6.4] - 2018-11-30 | ||||
| ### Fixed | ||||
|  | ||||
| - Fix yarn dependencies not installing due to yanked event-stream package (#9401) | ||||
|  | ||||
| ## [2.6.3] - 2018-11-30 | ||||
| ### Added | ||||
|  | ||||
| - Add hyphen to characters allowed in remote usernames (#9345) | ||||
|  | ||||
| ### Changed | ||||
|  | ||||
| - Change server user count to exclude suspended accounts (#9380) | ||||
|  | ||||
| ### Fixed | ||||
|  | ||||
| - Fix ffmpeg processing sometimes stalling due to overfilled stdout buffer (#9368) | ||||
| - Fix missing DNS records raising the wrong kind of exception (#9379) | ||||
| - Fix already queued deliveries still trying to reach inboxes marked as unavailable (#9358) | ||||
|  | ||||
| ### Security | ||||
|  | ||||
| - Fix TLS handshake timeout not being enforced (#9381) | ||||
|  | ||||
| ## [2.6.2] - 2018-11-23 | ||||
| ### Added | ||||
|  | ||||
| - Add Page to whitelisted ActivityPub types (#9188) | ||||
| - Add 20px to column width in web UI (#9227) | ||||
| - Add amount of freed disk space in `tootctl media remove` (#9229, #9239, #9288) | ||||
| - Add "Show thread" link to self-replies (#9228) | ||||
|  | ||||
| ### Changed | ||||
|  | ||||
| - Change order of Atom and RSS links so Atom is first (#9302) | ||||
| - Change Nginx configuration for Nanobox apps (#9310) | ||||
| - Change the follow action to appear instant in web UI (#9220) | ||||
| - Change how the ActiveRecord connection is instantiated in on_worker_boot (#9238) | ||||
| - Change `tootctl accounts cull` to always touch accounts so they can be skipped (#9293) | ||||
| - Change mime type comparison to ignore JSON-LD profile (#9179) | ||||
|  | ||||
| ### Fixed | ||||
|  | ||||
| - Fix web UI crash when conversation has no last status (#9207) | ||||
| - Fix follow limit validator reporting lower number past threshold (#9230) | ||||
| - Fix form validation flash message color and input borders (#9235) | ||||
| - Fix invalid twitter:player cards being displayed (#9254) | ||||
| - Fix emoji update date being processed incorrectly (#9255) | ||||
| - Fix playing embed resetting if status is reloaded in web UI (#9270, #9275) | ||||
| - Fix web UI crash when favouriting a deleted status (#9272) | ||||
| - Fix intermediary arrays being created for hash maps (#9291) | ||||
| - Fix filter ID not being a string in REST API (#9303) | ||||
|  | ||||
| ### Security | ||||
|  | ||||
| - Fix multiple remote account deletions being able to deadlock the database (#9292) | ||||
| - Fix HTTP connection timeout of 10s not being enforced (#9329) | ||||
|  | ||||
| ## [2.6.1] - 2018-10-30 | ||||
| ### Fixed | ||||
|  | ||||
|   | ||||
| @@ -17,7 +17,7 @@ class Api::V1::AccountsController < Api::BaseController | ||||
|   end | ||||
|  | ||||
|   def follow | ||||
|     FollowService.new.call(current_user.account, @account.acct, reblogs: truthy_param?(:reblogs)) | ||||
|     FollowService.new.call(current_user.account, @account, reblogs: truthy_param?(:reblogs)) | ||||
|  | ||||
|     options = @account.locked? ? {} : { following_map: { @account.id => { reblogs: truthy_param?(:reblogs) } }, requested_map: { @account.id => false } } | ||||
|  | ||||
|   | ||||
| @@ -113,7 +113,7 @@ class ApplicationController < ActionController::Base | ||||
|     klass.reload_stale_associations!(cached_keys_with_value.values) if klass.respond_to?(:reload_stale_associations!) | ||||
|  | ||||
|     unless uncached_ids.empty? | ||||
|       uncached = klass.where(id: uncached_ids).with_includes.map { |item| [item.id, item] }.to_h | ||||
|       uncached = klass.where(id: uncached_ids).with_includes.each_with_object({}) { |item, h| h[item.id] = item } | ||||
|  | ||||
|       uncached.each_value do |item| | ||||
|         Rails.cache.write(item, item) | ||||
|   | ||||
| @@ -145,12 +145,14 @@ export function fetchAccountFail(id, error) { | ||||
| export function followAccount(id, reblogs = true) { | ||||
|   return (dispatch, getState) => { | ||||
|     const alreadyFollowing = getState().getIn(['relationships', id, 'following']); | ||||
|     dispatch(followAccountRequest(id)); | ||||
|     const locked = getState().getIn(['accounts', id, 'locked'], false); | ||||
|  | ||||
|     dispatch(followAccountRequest(id, locked)); | ||||
|  | ||||
|     api(getState).post(`/api/v1/accounts/${id}/follow`, { reblogs }).then(response => { | ||||
|       dispatch(followAccountSuccess(response.data, alreadyFollowing)); | ||||
|     }).catch(error => { | ||||
|       dispatch(followAccountFail(error)); | ||||
|       dispatch(followAccountFail(error, locked)); | ||||
|     }); | ||||
|   }; | ||||
| }; | ||||
| @@ -167,10 +169,12 @@ export function unfollowAccount(id) { | ||||
|   }; | ||||
| }; | ||||
|  | ||||
| export function followAccountRequest(id) { | ||||
| export function followAccountRequest(id, locked) { | ||||
|   return { | ||||
|     type: ACCOUNT_FOLLOW_REQUEST, | ||||
|     id, | ||||
|     locked, | ||||
|     skipLoading: true, | ||||
|   }; | ||||
| }; | ||||
|  | ||||
| @@ -179,13 +183,16 @@ export function followAccountSuccess(relationship, alreadyFollowing) { | ||||
|     type: ACCOUNT_FOLLOW_SUCCESS, | ||||
|     relationship, | ||||
|     alreadyFollowing, | ||||
|     skipLoading: true, | ||||
|   }; | ||||
| }; | ||||
|  | ||||
| export function followAccountFail(error) { | ||||
| export function followAccountFail(error, locked) { | ||||
|   return { | ||||
|     type: ACCOUNT_FOLLOW_FAIL, | ||||
|     error, | ||||
|     locked, | ||||
|     skipLoading: true, | ||||
|   }; | ||||
| }; | ||||
|  | ||||
| @@ -193,6 +200,7 @@ export function unfollowAccountRequest(id) { | ||||
|   return { | ||||
|     type: ACCOUNT_UNFOLLOW_REQUEST, | ||||
|     id, | ||||
|     skipLoading: true, | ||||
|   }; | ||||
| }; | ||||
|  | ||||
| @@ -201,6 +209,7 @@ export function unfollowAccountSuccess(relationship, statuses) { | ||||
|     type: ACCOUNT_UNFOLLOW_SUCCESS, | ||||
|     relationship, | ||||
|     statuses, | ||||
|     skipLoading: true, | ||||
|   }; | ||||
| }; | ||||
|  | ||||
| @@ -208,6 +217,7 @@ export function unfollowAccountFail(error) { | ||||
|   return { | ||||
|     type: ACCOUNT_UNFOLLOW_FAIL, | ||||
|     error, | ||||
|     skipLoading: true, | ||||
|   }; | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -67,6 +67,7 @@ class Status extends ImmutablePureComponent { | ||||
|     unread: PropTypes.bool, | ||||
|     onMoveUp: PropTypes.func, | ||||
|     onMoveDown: PropTypes.func, | ||||
|     showThread: PropTypes.bool, | ||||
|   }; | ||||
|  | ||||
|   // Avoid checking props that are functions (and whose equality will always | ||||
| @@ -168,7 +169,7 @@ class Status extends ImmutablePureComponent { | ||||
|     let media = null; | ||||
|     let statusAvatar, prepend, rebloggedByText; | ||||
|  | ||||
|     const { intl, hidden, featured, otherAccounts, unread } = this.props; | ||||
|     const { intl, hidden, featured, otherAccounts, unread, showThread } = this.props; | ||||
|  | ||||
|     let { status, account, ...other } = this.props; | ||||
|  | ||||
| @@ -309,6 +310,12 @@ class Status extends ImmutablePureComponent { | ||||
|  | ||||
|             {media} | ||||
|  | ||||
|             {showThread && status.get('in_reply_to_id') && status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) && ( | ||||
|               <button className='status__content__read-more-button' onClick={this.handleClick}> | ||||
|                 <FormattedMessage id='status.show_thread' defaultMessage='Show thread' /> | ||||
|               </button> | ||||
|             )} | ||||
|  | ||||
|             <StatusActionBar status={status} account={account} {...other} /> | ||||
|           </div> | ||||
|         </div> | ||||
|   | ||||
| @@ -148,7 +148,6 @@ class StatusActionBar extends ImmutablePureComponent { | ||||
|  | ||||
|     let menu = []; | ||||
|     let reblogIcon = 'retweet'; | ||||
|     let replyIcon; | ||||
|     let replyTitle; | ||||
|  | ||||
|     menu.push({ text: intl.formatMessage(messages.open), action: this.handleOpen }); | ||||
| @@ -191,10 +190,8 @@ class StatusActionBar extends ImmutablePureComponent { | ||||
|     } | ||||
|  | ||||
|     if (status.get('in_reply_to_id', null) === null) { | ||||
|       replyIcon = 'reply'; | ||||
|       replyTitle = intl.formatMessage(messages.reply); | ||||
|     } else { | ||||
|       replyIcon = 'reply-all'; | ||||
|       replyTitle = intl.formatMessage(messages.replyAll); | ||||
|     } | ||||
|  | ||||
| @@ -204,7 +201,7 @@ class StatusActionBar extends ImmutablePureComponent { | ||||
|  | ||||
|     return ( | ||||
|       <div className='status__action-bar'> | ||||
|         <div className='status__action-bar__counter'><IconButton className='status__action-bar-button' disabled={anonymousAccess} title={replyTitle} icon={replyIcon} onClick={this.handleReplyClick} /><span className='status__action-bar__counter__label' >{obfuscatedCount(status.get('replies_count'))}</span></div> | ||||
|         <div className='status__action-bar__counter'><IconButton className='status__action-bar-button' disabled={anonymousAccess} title={replyTitle} icon='reply' onClick={this.handleReplyClick} /><span className='status__action-bar__counter__label' >{obfuscatedCount(status.get('replies_count'))}</span></div> | ||||
|         <IconButton className='status__action-bar-button' disabled={anonymousAccess || !publicStatus} active={status.get('reblogged')} pressed={status.get('reblogged')} title={!publicStatus ? intl.formatMessage(messages.cannot_reblog) : intl.formatMessage(messages.reblog)} icon={reblogIcon} onClick={this.handleReblogClick} /> | ||||
|         <IconButton className='status__action-bar-button star-icon' disabled={anonymousAccess} animate active={status.get('favourited')} pressed={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} /> | ||||
|         {shareButton} | ||||
|   | ||||
| @@ -104,6 +104,7 @@ export default class StatusList extends ImmutablePureComponent { | ||||
|           onMoveUp={this.handleMoveUp} | ||||
|           onMoveDown={this.handleMoveDown} | ||||
|           contextType={timelineId} | ||||
|           showThread | ||||
|         /> | ||||
|       )) | ||||
|     ) : null; | ||||
| @@ -117,6 +118,7 @@ export default class StatusList extends ImmutablePureComponent { | ||||
|           onMoveUp={this.handleMoveUp} | ||||
|           onMoveDown={this.handleMoveDown} | ||||
|           contextType={timelineId} | ||||
|           showThread | ||||
|         /> | ||||
|       )).concat(scrollableContent); | ||||
|     } | ||||
|   | ||||
| @@ -159,7 +159,7 @@ class ActionBar extends React.PureComponent { | ||||
|  | ||||
|     return ( | ||||
|       <div className='detailed-status__action-bar'> | ||||
|         <div className='detailed-status__button'><IconButton title={intl.formatMessage(messages.reply)} icon={status.get('in_reply_to_id', null) === null ? 'reply' : 'reply-all'} onClick={this.handleReplyClick} /></div> | ||||
|         <div className='detailed-status__button'><IconButton title={intl.formatMessage(messages.reply)} icon='reply' onClick={this.handleReplyClick} /></div> | ||||
|         <div className='detailed-status__button'><IconButton disabled={reblog_disabled} active={status.get('reblogged')} title={reblog_disabled ? intl.formatMessage(messages.cannot_reblog) : intl.formatMessage(messages.reblog)} icon={reblogIcon} onClick={this.handleReblogClick} /></div> | ||||
|         <div className='detailed-status__button'><IconButton className='star-icon' animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} /></div> | ||||
|         {shareButton} | ||||
|   | ||||
| @@ -73,7 +73,7 @@ export default class Card extends React.PureComponent { | ||||
|   }; | ||||
|  | ||||
|   componentWillReceiveProps (nextProps) { | ||||
|     if (this.props.card !== nextProps.card) { | ||||
|     if (!Immutable.is(this.props.card, nextProps.card)) { | ||||
|       this.setState({ embedded: false }); | ||||
|     } | ||||
|   } | ||||
|   | ||||
| @@ -56,7 +56,13 @@ const expandNormalizedConversations = (state, conversations, next) => { | ||||
|  | ||||
|         list = list.concat(items); | ||||
|  | ||||
|         return list.sortBy(x => x.get('last_status'), (a, b) => compareId(a, b) * -1); | ||||
|         return list.sortBy(x => x.get('last_status'), (a, b) => { | ||||
|           if(a === null || b === null) { | ||||
|             return -1; | ||||
|           } | ||||
|  | ||||
|           return compareId(a, b) * -1; | ||||
|         }); | ||||
|       }); | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -1,6 +1,10 @@ | ||||
| import { | ||||
|   ACCOUNT_FOLLOW_SUCCESS, | ||||
|   ACCOUNT_FOLLOW_REQUEST, | ||||
|   ACCOUNT_FOLLOW_FAIL, | ||||
|   ACCOUNT_UNFOLLOW_SUCCESS, | ||||
|   ACCOUNT_UNFOLLOW_REQUEST, | ||||
|   ACCOUNT_UNFOLLOW_FAIL, | ||||
|   ACCOUNT_BLOCK_SUCCESS, | ||||
|   ACCOUNT_UNBLOCK_SUCCESS, | ||||
|   ACCOUNT_MUTE_SUCCESS, | ||||
| @@ -37,6 +41,14 @@ const initialState = ImmutableMap(); | ||||
|  | ||||
| export default function relationships(state = initialState, action) { | ||||
|   switch(action.type) { | ||||
|   case ACCOUNT_FOLLOW_REQUEST: | ||||
|     return state.setIn([action.id, action.locked ? 'requested' : 'following'], true); | ||||
|   case ACCOUNT_FOLLOW_FAIL: | ||||
|     return state.setIn([action.id, action.locked ? 'requested' : 'following'], false); | ||||
|   case ACCOUNT_UNFOLLOW_REQUEST: | ||||
|     return state.setIn([action.id, 'following'], false); | ||||
|   case ACCOUNT_UNFOLLOW_FAIL: | ||||
|     return state.setIn([action.id, 'following'], true); | ||||
|   case ACCOUNT_FOLLOW_SUCCESS: | ||||
|   case ACCOUNT_UNFOLLOW_SUCCESS: | ||||
|   case ACCOUNT_BLOCK_SUCCESS: | ||||
|   | ||||
| @@ -38,11 +38,11 @@ export default function statuses(state = initialState, action) { | ||||
|   case FAVOURITE_REQUEST: | ||||
|     return state.setIn([action.status.get('id'), 'favourited'], true); | ||||
|   case FAVOURITE_FAIL: | ||||
|     return state.setIn([action.status.get('id'), 'favourited'], false); | ||||
|     return state.get(action.status.get('id')) === undefined ? state : state.setIn([action.status.get('id'), 'favourited'], false); | ||||
|   case REBLOG_REQUEST: | ||||
|     return state.setIn([action.status.get('id'), 'reblogged'], true); | ||||
|   case REBLOG_FAIL: | ||||
|     return state.setIn([action.status.get('id'), 'reblogged'], false); | ||||
|     return state.get(action.status.get('id')) === undefined ? state : state.setIn([action.status.get('id'), 'reblogged'], false); | ||||
|   case STATUS_MUTE_SUCCESS: | ||||
|     return state.setIn([action.id, 'muted'], true); | ||||
|   case STATUS_UNMUTE_SUCCESS: | ||||
|   | ||||
| @@ -1847,7 +1847,7 @@ a.account__display-name { | ||||
| } | ||||
|  | ||||
| .column { | ||||
|   width: 330px; | ||||
|   width: 350px; | ||||
|   position: relative; | ||||
|   box-sizing: border-box; | ||||
|   display: flex; | ||||
|   | ||||
| @@ -330,9 +330,12 @@ code { | ||||
|     } | ||||
|  | ||||
|     input[type=text], | ||||
|     input[type=number], | ||||
|     input[type=email], | ||||
|     input[type=password] { | ||||
|       border-bottom-color: $valid-value-color; | ||||
|     input[type=password], | ||||
|     textarea, | ||||
|     select { | ||||
|       border-color: lighten($error-red, 12%); | ||||
|     } | ||||
|  | ||||
|     .error { | ||||
|   | ||||
| @@ -129,4 +129,10 @@ class ActivityPub::Activity | ||||
|       ::FetchRemoteStatusService.new.call(@object['url']) | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   def lock_or_return(key, expire_after = 7.days.seconds) | ||||
|     yield if redis.set(key, true, nx: true, ex: expire_after) | ||||
|   ensure | ||||
|     redis.del(key) | ||||
|   end | ||||
| end | ||||
|   | ||||
| @@ -177,7 +177,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity | ||||
|     updated   = tag['updated'] | ||||
|     emoji     = CustomEmoji.find_by(shortcode: shortcode, domain: @account.domain) | ||||
|  | ||||
|     return unless emoji.nil? || image_url != emoji.image_remote_url || (updated && emoji.updated_at >= updated) | ||||
|     return unless emoji.nil? || image_url != emoji.image_remote_url || (updated && updated >= emoji.updated_at) | ||||
|  | ||||
|     emoji ||= CustomEmoji.new(domain: @account.domain, shortcode: shortcode, uri: uri) | ||||
|     emoji.image_remote_url = image_url | ||||
|   | ||||
| @@ -12,8 +12,10 @@ class ActivityPub::Activity::Delete < ActivityPub::Activity | ||||
|   private | ||||
|  | ||||
|   def delete_person | ||||
|     SuspendAccountService.new.call(@account) | ||||
|     @account.destroy! | ||||
|     lock_or_return("delete_in_progress:#{@account.id}") do | ||||
|       SuspendAccountService.new.call(@account) | ||||
|       @account.destroy! | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   def delete_note | ||||
|   | ||||
| @@ -21,7 +21,7 @@ class EntityCache | ||||
|     end | ||||
|  | ||||
|     unless uncached_ids.empty? | ||||
|       uncached = CustomEmoji.where(shortcode: shortcodes, domain: domain, disabled: false).map { |item| [item.shortcode, item] }.to_h | ||||
|       uncached = CustomEmoji.where(shortcode: shortcodes, domain: domain, disabled: false).each_with_object({}) { |item, h| h[item.shortcode] = item } | ||||
|       uncached.each_value { |item| Rails.cache.write(to_key(:emoji, item.shortcode, domain), item, expires_in: MAX_EXPIRATION) } | ||||
|     end | ||||
|  | ||||
|   | ||||
| @@ -128,9 +128,9 @@ class Formatter | ||||
|     return html if emojis.empty? | ||||
|  | ||||
|     emoji_map = if animate | ||||
|                   emojis.map { |e| [e.shortcode, full_asset_url(e.image.url)] }.to_h | ||||
|                   emojis.each_with_object({}) { |e, h| h[e.shortcode] = full_asset_url(e.image.url) } | ||||
|                 else | ||||
|                   emojis.map { |e| [e.shortcode, full_asset_url(e.image.url(:static))] }.to_h | ||||
|                   emojis.each_with_object({}) { |e, h| h[e.shortcode] = full_asset_url(e.image.url(:static)) } | ||||
|                 end | ||||
|  | ||||
|     i                     = -1 | ||||
|   | ||||
| @@ -2,6 +2,17 @@ | ||||
|  | ||||
| require 'ipaddr' | ||||
| require 'socket' | ||||
| require 'resolv' | ||||
|  | ||||
| # Monkey-patch the HTTP.rb timeout class to avoid using a timeout block | ||||
| # around the Socket#open method, since we use our own timeout blocks inside | ||||
| # that method | ||||
| class HTTP::Timeout::PerOperation | ||||
|   def connect(socket_class, host, port, nodelay = false) | ||||
|     @socket = socket_class.open(host, port) | ||||
|     @socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1) if nodelay | ||||
|   end | ||||
| end | ||||
|  | ||||
| class Request | ||||
|   REQUEST_TARGET = '(request-target)' | ||||
| @@ -45,7 +56,7 @@ class Request | ||||
|     end | ||||
|  | ||||
|     begin | ||||
|       yield response.extend(ClientLimit) | ||||
|       yield response.extend(ClientLimit) if block_given? | ||||
|     ensure | ||||
|       http_client.close | ||||
|     end | ||||
| @@ -94,7 +105,11 @@ class Request | ||||
|   end | ||||
|  | ||||
|   def timeout | ||||
|     { write: 10, connect: 10, read: 10 } | ||||
|     # We enforce a 1s timeout on DNS resolving, 10s timeout on socket opening | ||||
|     # and 5s timeout on the TLS handshake, meaning the worst case should take | ||||
|     # about 16s in total | ||||
|  | ||||
|     { connect: 5, read: 10, write: 10 } | ||||
|   end | ||||
|  | ||||
|   def http_client | ||||
| @@ -139,17 +154,34 @@ class Request | ||||
|   class Socket < TCPSocket | ||||
|     class << self | ||||
|       def open(host, *args) | ||||
|         return super host, *args if thru_hidden_service? host | ||||
|         return super(host, *args) if thru_hidden_service?(host) | ||||
|  | ||||
|         outer_e = nil | ||||
|         Addrinfo.foreach(host, nil, nil, :SOCK_STREAM) do |address| | ||||
|           begin | ||||
|             raise Mastodon::HostValidationError if PrivateAddressCheck.private_address? IPAddr.new(address.ip_address) | ||||
|             return super address.ip_address, *args | ||||
|           rescue => e | ||||
|             outer_e = e | ||||
|  | ||||
|         Resolv::DNS.open do |dns| | ||||
|           dns.timeouts = 1 | ||||
|  | ||||
|           addresses = dns.getaddresses(host).take(2) | ||||
|           time_slot = 10.0 / addresses.size | ||||
|  | ||||
|           addresses.each do |address| | ||||
|             begin | ||||
|               raise Mastodon::HostValidationError if PrivateAddressCheck.private_address?(IPAddr.new(address.to_s)) | ||||
|  | ||||
|               ::Timeout.timeout(time_slot, HTTP::TimeoutError) do | ||||
|                 return super(address.to_s, *args) | ||||
|               end | ||||
|             rescue => e | ||||
|               outer_e = e | ||||
|             end | ||||
|           end | ||||
|         end | ||||
|         raise outer_e if outer_e | ||||
|  | ||||
|         if outer_e | ||||
|           raise outer_e | ||||
|         else | ||||
|           raise SocketError, "No address for #{host}" | ||||
|         end | ||||
|       end | ||||
|  | ||||
|       alias new open | ||||
|   | ||||
| @@ -31,7 +31,7 @@ module Settings | ||||
|  | ||||
|     def all_as_records | ||||
|       vars = thing_scoped | ||||
|       records = vars.map { |r| [r.var, r] }.to_h | ||||
|       records = vars.each_with_object({}) { |r, h| h[r.var] = r } | ||||
|  | ||||
|       Setting.default_settings.each do |key, default_value| | ||||
|         next if records.key?(key) || default_value.is_a?(Hash) | ||||
| @@ -65,7 +65,7 @@ module Settings | ||||
|  | ||||
|     class << self | ||||
|       def default_settings | ||||
|         defaulting = DEFAULTING_TO_UNSCOPED.map { |k| [k, Setting[k]] }.to_h | ||||
|         defaulting = DEFAULTING_TO_UNSCOPED.each_with_object({}) { |k, h| h[k] = Setting[k] } | ||||
|         Setting.default_settings.merge!(defaulting) | ||||
|       end | ||||
|     end | ||||
|   | ||||
| @@ -49,7 +49,7 @@ | ||||
| # | ||||
|  | ||||
| class Account < ApplicationRecord | ||||
|   USERNAME_RE = /[a-z0-9_]+([a-z0-9_\.]+[a-z0-9_]+)?/i | ||||
|   USERNAME_RE = /[a-z0-9_]+([a-z0-9_\.-]+[a-z0-9_]+)?/i | ||||
|   MENTION_RE  = /(?<=^|[^\/[:word:]])@((#{USERNAME_RE})(?:@[a-z0-9\.\-]+[a-z0-9]+)?)/i | ||||
|  | ||||
|   include AccountAvatar | ||||
|   | ||||
| @@ -45,9 +45,9 @@ module AccountInteractions | ||||
|     end | ||||
|  | ||||
|     def domain_blocking_map(target_account_ids, account_id) | ||||
|       accounts_map    = Account.where(id: target_account_ids).select('id, domain').map { |a| [a.id, a.domain] }.to_h | ||||
|       accounts_map    = Account.where(id: target_account_ids).select('id, domain').each_with_object({}) { |a, h| h[a.id] = a.domain } | ||||
|       blocked_domains = domain_blocking_map_by_domain(accounts_map.values.compact, account_id) | ||||
|       accounts_map.map { |id, domain| [id, blocked_domains[domain]] }.to_h | ||||
|       accounts_map.reduce({}) { |h, (id, domain)| h.merge(id => blocked_domains[domain]) } | ||||
|     end | ||||
|  | ||||
|     def domain_blocking_map_by_domain(target_domains, account_id) | ||||
|   | ||||
| @@ -59,6 +59,7 @@ class MediaAttachment < ApplicationRecord | ||||
|     format: 'mp4', | ||||
|     convert_options: { | ||||
|       output: { | ||||
|         'loglevel' => 'fatal', | ||||
|         'movflags' => 'faststart', | ||||
|         'pix_fmt'  => 'yuv420p', | ||||
|         'vf'       => 'scale=\'trunc(iw/2)*2:trunc(ih/2)*2\'', | ||||
|   | ||||
| @@ -75,7 +75,7 @@ class Notification < ApplicationRecord | ||||
|  | ||||
|       return if account_ids.empty? | ||||
|  | ||||
|       accounts = Account.where(id: account_ids).map { |a| [a.id, a] }.to_h | ||||
|       accounts = Account.where(id: account_ids).each_with_object({}) { |a, h| h[a.id] = a } | ||||
|  | ||||
|       cached_items.each do |item| | ||||
|         item.from_account = accounts[item.from_account_id] | ||||
|   | ||||
| @@ -40,7 +40,7 @@ class Setting < RailsSettings::Base | ||||
|  | ||||
|     def all_as_records | ||||
|       vars    = thing_scoped | ||||
|       records = vars.map { |r| [r.var, r] }.to_h | ||||
|       records = vars.each_with_object({}) { |r, h| h[r.var] = r } | ||||
|  | ||||
|       default_settings.each do |key, default_value| | ||||
|         next if records.key?(key) || default_value.is_a?(Hash) | ||||
|   | ||||
| @@ -310,19 +310,19 @@ class Status < ApplicationRecord | ||||
|     end | ||||
|  | ||||
|     def favourites_map(status_ids, account_id) | ||||
|       Favourite.select('status_id').where(status_id: status_ids).where(account_id: account_id).map { |f| [f.status_id, true] }.to_h | ||||
|       Favourite.select('status_id').where(status_id: status_ids).where(account_id: account_id).each_with_object({}) { |f, h| h[f.status_id] = true } | ||||
|     end | ||||
|  | ||||
|     def reblogs_map(status_ids, account_id) | ||||
|       select('reblog_of_id').where(reblog_of_id: status_ids).where(account_id: account_id).reorder(nil).map { |s| [s.reblog_of_id, true] }.to_h | ||||
|       select('reblog_of_id').where(reblog_of_id: status_ids).where(account_id: account_id).reorder(nil).each_with_object({}) { |s, h| h[s.reblog_of_id] = true } | ||||
|     end | ||||
|  | ||||
|     def mutes_map(conversation_ids, account_id) | ||||
|       ConversationMute.select('conversation_id').where(conversation_id: conversation_ids).where(account_id: account_id).map { |m| [m.conversation_id, true] }.to_h | ||||
|       ConversationMute.select('conversation_id').where(conversation_id: conversation_ids).where(account_id: account_id).each_with_object({}) { |m, h| h[m.conversation_id] = true } | ||||
|     end | ||||
|  | ||||
|     def pins_map(status_ids, account_id) | ||||
|       StatusPin.select('status_id').where(status_id: status_ids).where(account_id: account_id).map { |p| [p.status_id, true] }.to_h | ||||
|       StatusPin.select('status_id').where(status_id: status_ids).where(account_id: account_id).each_with_object({}) { |p, h| h[p.status_id] = true } | ||||
|     end | ||||
|  | ||||
|     def reload_stale_associations!(cached_items) | ||||
| @@ -337,7 +337,7 @@ class Status < ApplicationRecord | ||||
|  | ||||
|       return if account_ids.empty? | ||||
|  | ||||
|       accounts = Account.where(id: account_ids).map { |a| [a.id, a] }.to_h | ||||
|       accounts = Account.where(id: account_ids).each_with_object({}) { |a, h| h[a.id] = a } | ||||
|  | ||||
|       cached_items.each do |item| | ||||
|         item.account = accounts[item.account_id] | ||||
|   | ||||
| @@ -18,7 +18,7 @@ class TrendingTags | ||||
|     def get(limit) | ||||
|       key     = "#{KEY}:#{Time.now.utc.beginning_of_day.to_i}" | ||||
|       tag_ids = redis.zrevrange(key, 0, limit - 1).map(&:to_i) | ||||
|       tags    = Tag.where(id: tag_ids).to_a.map { |tag| [tag.id, tag] }.to_h | ||||
|       tags    = Tag.where(id: tag_ids).to_a.each_with_object({}) { |tag, h| h[tag.id] = tag } | ||||
|       tag_ids.map { |tag_id| tags[tag_id] }.compact | ||||
|     end | ||||
|  | ||||
|   | ||||
| @@ -18,7 +18,7 @@ class InstancePresenter | ||||
|   end | ||||
|  | ||||
|   def user_count | ||||
|     Rails.cache.fetch('user_count') { User.confirmed.count } | ||||
|     Rails.cache.fetch('user_count') { User.confirmed.joins(:account).merge(Account.without_suspended).count } | ||||
|   end | ||||
|  | ||||
|   def status_count | ||||
|   | ||||
| @@ -3,4 +3,8 @@ | ||||
| class REST::FilterSerializer < ActiveModel::Serializer | ||||
|   attributes :id, :phrase, :context, :whole_word, :expires_at, | ||||
|              :irreversible | ||||
|  | ||||
|   def id | ||||
|     object.id.to_s | ||||
|   end | ||||
| end | ||||
|   | ||||
| @@ -232,7 +232,7 @@ class ActivityPub::ProcessAccountService < BaseService | ||||
|     updated   = tag['updated'] | ||||
|     emoji     = CustomEmoji.find_by(shortcode: shortcode, domain: @account.domain) | ||||
|  | ||||
|     return unless emoji.nil? || image_url != emoji.image_remote_url || (updated && emoji.updated_at >= updated) | ||||
|     return unless emoji.nil? || image_url != emoji.image_remote_url || (updated && updated >= emoji.updated_at) | ||||
|  | ||||
|     emoji ||= CustomEmoji.new(domain: @account.domain, shortcode: shortcode, uri: uri) | ||||
|     emoji.image_remote_url = image_url | ||||
|   | ||||
| @@ -12,12 +12,12 @@ class BatchedRemoveStatusService < BaseService | ||||
|   def call(statuses) | ||||
|     statuses = Status.where(id: statuses.map(&:id)).includes(:account, :stream_entry).flat_map { |status| [status] + status.reblogs.includes(:account, :stream_entry).to_a } | ||||
|  | ||||
|     @mentions = statuses.map { |s| [s.id, s.active_mentions.includes(:account).to_a] }.to_h | ||||
|     @tags     = statuses.map { |s| [s.id, s.tags.pluck(:name)] }.to_h | ||||
|     @mentions = statuses.each_with_object({}) { |s, h| h[s.id] = s.active_mentions.includes(:account).to_a } | ||||
|     @tags     = statuses.each_with_object({}) { |s, h| h[s.id] = s.tags.pluck(:name) } | ||||
|  | ||||
|     @stream_entry_batches  = [] | ||||
|     @salmon_batches        = [] | ||||
|     @json_payloads         = statuses.map { |s| [s.id, Oj.dump(event: :delete, payload: s.id.to_s)] }.to_h | ||||
|     @json_payloads         = statuses.each_with_object({}) { |s, h| h[s.id] = Oj.dump(event: :delete, payload: s.id.to_s) } | ||||
|     @activity_xml          = {} | ||||
|  | ||||
|     # Ensure that rendered XML reflects destroyed state | ||||
|   | ||||
| @@ -18,6 +18,6 @@ module AuthorExtractor | ||||
|       acct   = "#{username}@#{domain}" | ||||
|     end | ||||
|  | ||||
|     ResolveAccountService.new.call(acct, update_profile) | ||||
|     ResolveAccountService.new.call(acct, update_profile: update_profile) | ||||
|   end | ||||
| end | ||||
|   | ||||
| @@ -29,7 +29,7 @@ class FetchAtomService < BaseService | ||||
|  | ||||
|   def perform_request(&block) | ||||
|     accept = 'text/html' | ||||
|     accept = 'application/activity+json, application/ld+json, application/atom+xml, ' + accept unless @unsupported_activity | ||||
|     accept = 'application/activity+json, application/ld+json; profile="https://www.w3.org/ns/activitystreams", application/atom+xml, ' + accept unless @unsupported_activity | ||||
|  | ||||
|     Request.new(:get, @url).add_headers('Accept' => accept).perform(&block) | ||||
|   end | ||||
| @@ -39,7 +39,7 @@ class FetchAtomService < BaseService | ||||
|  | ||||
|     if response.mime_type == 'application/atom+xml' | ||||
|       [@url, { prefetched_body: response.body_with_limit }, :ostatus] | ||||
|     elsif ['application/activity+json', 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'].include?(response.mime_type) | ||||
|     elsif ['application/activity+json', 'application/ld+json'].include?(response.mime_type) | ||||
|       body = response.body_with_limit | ||||
|       json = body_to_json(body) | ||||
|       if supported_context?(json) && equals_or_includes_any?(json['type'], ActivityPub::FetchRemoteAccountService::SUPPORTED_TYPES) && json['inbox'].present? | ||||
|   | ||||
| @@ -136,14 +136,15 @@ class FetchLinkCardService < BaseService | ||||
|     detector = CharlockHolmes::EncodingDetector.new | ||||
|     detector.strip_tags = true | ||||
|  | ||||
|     guess = detector.detect(@html, @html_charset) | ||||
|     page  = Nokogiri::HTML(@html, nil, guess&.fetch(:encoding, nil)) | ||||
|     guess      = detector.detect(@html, @html_charset) | ||||
|     page       = Nokogiri::HTML(@html, nil, guess&.fetch(:encoding, nil)) | ||||
|     player_url = meta_property(page, 'twitter:player') | ||||
|  | ||||
|     if meta_property(page, 'twitter:player') | ||||
|     if player_url && !bad_url?(Addressable::URI.parse(player_url)) | ||||
|       @card.type   = :video | ||||
|       @card.width  = meta_property(page, 'twitter:player:width') || 0 | ||||
|       @card.height = meta_property(page, 'twitter:player:height') || 0 | ||||
|       @card.html   = content_tag(:iframe, nil, src: meta_property(page, 'twitter:player'), | ||||
|       @card.html   = content_tag(:iframe, nil, src: player_url, | ||||
|                                                width: @card.width, | ||||
|                                                height: @card.height, | ||||
|                                                allowtransparency: 'true', | ||||
|   | ||||
| @@ -7,9 +7,9 @@ class FollowService < BaseService | ||||
|   # @param [Account] source_account From which to follow | ||||
|   # @param [String, Account] uri User URI to follow in the form of username@domain (or account record) | ||||
|   # @param [true, false, nil] reblogs Whether or not to show reblogs, defaults to true | ||||
|   def call(source_account, uri, reblogs: nil) | ||||
|   def call(source_account, target_account, reblogs: nil) | ||||
|     reblogs = true if reblogs.nil? | ||||
|     target_account = uri.is_a?(Account) ? uri : ResolveAccountService.new.call(uri) | ||||
|     target_account = ResolveAccountService.new.call(target_account, skip_webfinger: true) | ||||
|  | ||||
|     raise ActiveRecord::RecordNotFound if target_account.nil? || target_account.id == source_account.id || target_account.suspended? | ||||
|     raise Mastodon::NotPermittedError  if target_account.blocking?(source_account) || source_account.blocking?(target_account) | ||||
| @@ -42,7 +42,7 @@ class FollowService < BaseService | ||||
|     follow_request = FollowRequest.create!(account: source_account, target_account: target_account, show_reblogs: reblogs) | ||||
|  | ||||
|     if target_account.local? | ||||
|       NotifyService.new.call(target_account, follow_request) | ||||
|       LocalNotificationWorker.perform_async(target_account.id, follow_request.id, follow_request.class.name) | ||||
|     elsif target_account.ostatus? | ||||
|       NotificationWorker.perform_async(build_follow_request_xml(follow_request), source_account.id, target_account.id) | ||||
|       AfterRemoteFollowRequestWorker.perform_async(follow_request.id) | ||||
| @@ -57,7 +57,7 @@ class FollowService < BaseService | ||||
|     follow = source_account.follow!(target_account, reblogs: reblogs) | ||||
|  | ||||
|     if target_account.local? | ||||
|       NotifyService.new.call(target_account, follow) | ||||
|       LocalNotificationWorker.perform_async(target_account.id, follow.id, follow.class.name) | ||||
|     else | ||||
|       Pubsubhubbub::SubscribeWorker.perform_async(target_account.id) unless target_account.subscribed? | ||||
|       NotificationWorker.perform_async(build_follow_xml(follow), source_account.id, target_account.id) | ||||
|   | ||||
| @@ -47,7 +47,7 @@ class ProcessMentionsService < BaseService | ||||
|     mentioned_account = mention.account | ||||
|  | ||||
|     if mentioned_account.local? | ||||
|       LocalNotificationWorker.perform_async(mention.id) | ||||
|       LocalNotificationWorker.perform_async(mentioned_account.id, mention.id, mention.class.name) | ||||
|     elsif mentioned_account.ostatus? && !@status.stream_entry.hidden? | ||||
|       NotificationWorker.perform_async(ostatus_xml, @status.account_id, mentioned_account.id) | ||||
|     elsif mentioned_account.activitypub? | ||||
|   | ||||
| @@ -9,17 +9,27 @@ class ResolveAccountService < BaseService | ||||
|   # Find or create a local account for a remote user. | ||||
|   # When creating, look up the user's webfinger and fetch all | ||||
|   # important information from their feed | ||||
|   # @param [String] uri User URI in the form of username@domain | ||||
|   # @param [String, Account] uri User URI in the form of username@domain | ||||
|   # @param [Hash] options | ||||
|   # @return [Account] | ||||
|   def call(uri, update_profile = true, redirected = nil) | ||||
|     @username, @domain = uri.split('@') | ||||
|     @update_profile    = update_profile | ||||
|   def call(uri, options = {}) | ||||
|     @options = options | ||||
|  | ||||
|     return Account.find_local(@username) if TagManager.instance.local_domain?(@domain) | ||||
|     if uri.is_a?(Account) | ||||
|       @account  = uri | ||||
|       @username = @account.username | ||||
|       @domain   = @account.domain | ||||
|  | ||||
|     @account = Account.find_remote(@username, @domain) | ||||
|       return @account if @account.local? || !webfinger_update_due? | ||||
|     else | ||||
|       @username, @domain = uri.split('@') | ||||
|  | ||||
|     return @account unless webfinger_update_due? | ||||
|       return Account.find_local(@username) if TagManager.instance.local_domain?(@domain) | ||||
|  | ||||
|       @account = Account.find_remote(@username, @domain) | ||||
|  | ||||
|       return @account unless webfinger_update_due? | ||||
|     end | ||||
|  | ||||
|     Rails.logger.debug "Looking up webfinger for #{uri}" | ||||
|  | ||||
| @@ -30,8 +40,8 @@ class ResolveAccountService < BaseService | ||||
|     if confirmed_username.casecmp(@username).zero? && confirmed_domain.casecmp(@domain).zero? | ||||
|       @username = confirmed_username | ||||
|       @domain   = confirmed_domain | ||||
|     elsif redirected.nil? | ||||
|       return call("#{confirmed_username}@#{confirmed_domain}", update_profile, true) | ||||
|     elsif options[:redirected].nil? | ||||
|       return call("#{confirmed_username}@#{confirmed_domain}", options.merge(redirected: true)) | ||||
|     else | ||||
|       Rails.logger.debug 'Requested and returned acct URIs do not match' | ||||
|       return | ||||
| @@ -76,7 +86,7 @@ class ResolveAccountService < BaseService | ||||
|   end | ||||
|  | ||||
|   def webfinger_update_due? | ||||
|     @account.nil? || @account.possibly_stale? | ||||
|     @account.nil? || ((!@options[:skip_webfinger] || @account.ostatus?) && @account.possibly_stale?) | ||||
|   end | ||||
|  | ||||
|   def activitypub_ready? | ||||
| @@ -93,7 +103,7 @@ class ResolveAccountService < BaseService | ||||
|   end | ||||
|  | ||||
|   def update_profile? | ||||
|     @update_profile | ||||
|     @options[:update_profile] | ||||
|   end | ||||
|  | ||||
|   def handle_activitypub | ||||
|   | ||||
| @@ -20,7 +20,7 @@ class ResolveURLService < BaseService | ||||
|   def process_url | ||||
|     if equals_or_includes_any?(type, %w(Application Group Organization Person Service)) | ||||
|       FetchRemoteAccountService.new.call(atom_url, body, protocol) | ||||
|     elsif equals_or_includes_any?(type, %w(Note Article Image Video)) | ||||
|     elsif equals_or_includes_any?(type, %w(Note Article Image Video Page)) | ||||
|       FetchRemoteStatusService.new.call(atom_url, body, protocol) | ||||
|     end | ||||
|   end | ||||
|   | ||||
| @@ -14,7 +14,7 @@ class FollowLimitValidator < ActiveModel::Validator | ||||
|       if account.following_count < LIMIT | ||||
|         LIMIT | ||||
|       else | ||||
|         account.followers_count * RATIO | ||||
|         [(account.followers_count * RATIO).round, LIMIT].max | ||||
|       end | ||||
|     end | ||||
|   end | ||||
|   | ||||
| @@ -8,8 +8,8 @@ | ||||
|     %meta{ name: 'robots', content: 'noindex' }/ | ||||
|  | ||||
|   %link{ rel: 'salmon', href: api_salmon_url(@account.id) }/ | ||||
|   %link{ rel: 'alternate', type: 'application/rss+xml', href: account_url(@account, format: 'rss') }/ | ||||
|   %link{ rel: 'alternate', type: 'application/atom+xml', href: account_url(@account, format: 'atom') }/ | ||||
|   %link{ rel: 'alternate', type: 'application/rss+xml', href: account_url(@account, format: 'rss') }/ | ||||
|   %link{ rel: 'alternate', type: 'application/activity+json', href: ActivityPub::TagManager.instance.uri_for(@account) }/ | ||||
|  | ||||
|   - if @older_url | ||||
|   | ||||
| @@ -1,3 +1,3 @@ | ||||
| - if object.errors.any? | ||||
|   .flash-message#error_explanation | ||||
|   .flash-message.alert#error_explanation | ||||
|     %strong= t('generic.validation_errors', count: object.errors.count) | ||||
|   | ||||
| @@ -11,6 +11,8 @@ class ActivityPub::DeliveryWorker | ||||
|   HEADERS = { 'Content-Type' => 'application/activity+json' }.freeze | ||||
|  | ||||
|   def perform(json, source_account_id, inbox_url, options = {}) | ||||
|     return if DeliveryFailureTracker.unavailable?(inbox_url) | ||||
|  | ||||
|     @options        = options.with_indifferent_access | ||||
|     @json           = json | ||||
|     @source_account = Account.find(source_account_id) | ||||
|   | ||||
| @@ -3,9 +3,16 @@ | ||||
| class LocalNotificationWorker | ||||
|   include Sidekiq::Worker | ||||
|  | ||||
|   def perform(mention_id) | ||||
|     mention = Mention.find(mention_id) | ||||
|     NotifyService.new.call(mention.account, mention) | ||||
|   def perform(receiver_account_id, activity_id = nil, activity_class_name = nil) | ||||
|     if activity_id.nil? && activity_class_name.nil? | ||||
|       activity = Mention.find(receiver_account_id) | ||||
|       receiver = activity.account | ||||
|     else | ||||
|       receiver = Account.find(receiver_account_id) | ||||
|       activity = activity_class_name.constantize.find(activity_id) | ||||
|     end | ||||
|  | ||||
|     NotifyService.new.call(receiver, activity) | ||||
|   rescue ActiveRecord::RecordNotFound | ||||
|     true | ||||
|   end | ||||
|   | ||||
| @@ -13,7 +13,9 @@ workers     ENV.fetch('WEB_CONCURRENCY') { 2 } | ||||
| preload_app! | ||||
|  | ||||
| on_worker_boot do | ||||
|   ActiveRecord::Base.establish_connection if defined?(ActiveRecord) | ||||
|   ActiveSupport.on_load(:active_record) do | ||||
|     ActiveRecord::Base.establish_connection | ||||
|   end | ||||
| end | ||||
|  | ||||
| plugin :tmp_restart | ||||
|   | ||||
| @@ -242,8 +242,9 @@ module Mastodon | ||||
|           end | ||||
|  | ||||
|           culled += 1 | ||||
|           say('.', :green, false) | ||||
|           say('+', :green, false) | ||||
|         else | ||||
|           account.touch # Touch account even during dry run to avoid getting the account into the window again | ||||
|           say('.', nil, false) | ||||
|         end | ||||
|       end | ||||
|   | ||||
| @@ -6,6 +6,8 @@ require_relative 'cli_helper' | ||||
|  | ||||
| module Mastodon | ||||
|   class MediaCLI < Thor | ||||
|     include ActionView::Helpers::NumberHelper | ||||
|  | ||||
|     def self.exit_on_failure? | ||||
|       true | ||||
|     end | ||||
| @@ -36,16 +38,19 @@ module Mastodon | ||||
|       time_ago  = options[:days].days.ago | ||||
|       queued    = 0 | ||||
|       processed = 0 | ||||
|       dry_run = options[:dry_run] ? '(DRY RUN)' : '' | ||||
|       size      = 0 | ||||
|       dry_run   = options[:dry_run] ? '(DRY RUN)' : '' | ||||
|  | ||||
|       if options[:background] | ||||
|         MediaAttachment.where.not(remote_url: '').where.not(file_file_name: nil).where('created_at < ?', time_ago).select(:id).reorder(nil).find_in_batches do |media_attachments| | ||||
|         MediaAttachment.where.not(remote_url: '').where.not(file_file_name: nil).where('created_at < ?', time_ago).select(:id, :file_file_size).reorder(nil).find_in_batches do |media_attachments| | ||||
|           queued += media_attachments.size | ||||
|           size   += media_attachments.reduce(0) { |sum, m| sum + (m.file_file_size || 0) } | ||||
|           Maintenance::UncacheMediaWorker.push_bulk(media_attachments.map(&:id)) unless options[:dry_run] | ||||
|         end | ||||
|       else | ||||
|         MediaAttachment.where.not(remote_url: '').where.not(file_file_name: nil).where('created_at < ?', time_ago).reorder(nil).find_in_batches do |media_attachments| | ||||
|           media_attachments.each do |m| | ||||
|             size += m.file_file_size || 0 | ||||
|             Maintenance::UncacheMediaWorker.new.perform(m) unless options[:dry_run] | ||||
|             options[:verbose] ? say(m.id) : say('.', :green, false) | ||||
|             processed += 1 | ||||
| @@ -56,9 +61,9 @@ module Mastodon | ||||
|       say | ||||
|  | ||||
|       if options[:background] | ||||
|         say("Scheduled the deletion of #{queued} media attachments #{dry_run}", :green, true) | ||||
|         say("Scheduled the deletion of #{queued} media attachments (approx. #{number_to_human_size(size)}) #{dry_run}", :green, true) | ||||
|       else | ||||
|         say("Removed #{processed} media attachments #{dry_run}", :green, true) | ||||
|         say("Removed #{processed} media attachments (approx. #{number_to_human_size(size)}) #{dry_run}", :green, true) | ||||
|       end | ||||
|     end | ||||
|   end | ||||
|   | ||||
| @@ -13,7 +13,7 @@ module Mastodon | ||||
|     end | ||||
|  | ||||
|     def patch | ||||
|       1 | ||||
|       4 | ||||
|     end | ||||
|  | ||||
|     def pre | ||||
|   | ||||
| @@ -38,7 +38,7 @@ http { | ||||
|  | ||||
|         root /app/public; | ||||
|  | ||||
|         client_max_body_size 8M; | ||||
|         client_max_body_size 80M; | ||||
|  | ||||
|         location / { | ||||
|             try_files $uri @rails; | ||||
|   | ||||
| @@ -32,7 +32,7 @@ http { | ||||
|         listen 8080; | ||||
|  | ||||
|         add_header Strict-Transport-Security "max-age=31536000"; | ||||
|         add_header Content-Security-Policy "style-src 'self' 'unsafe-inline'; script-src 'self'; object-src 'self'; img-src data: https:; media-src data: https:; connect-src 'self' wss://<%= ENV["LOCAL_DOMAIN"] %>; upgrade-insecure-requests"; | ||||
|         # add_header Content-Security-Policy "style-src 'self' 'unsafe-inline'; script-src 'self'; object-src 'self'; img-src data: https:; media-src data: https:; connect-src 'self' wss://<%= ENV["LOCAL_DOMAIN"] %>; upgrade-insecure-requests"; | ||||
|  | ||||
|         root /app/public; | ||||
|  | ||||
|   | ||||
| @@ -32,11 +32,11 @@ http { | ||||
|         listen 8080; | ||||
|  | ||||
|         add_header Strict-Transport-Security "max-age=31536000"; | ||||
|         add_header Content-Security-Policy "style-src 'self' 'unsafe-inline'; script-src 'self'; object-src 'self'; img-src data: https:; media-src data: https:; connect-src 'self' wss://<%= ENV["LOCAL_DOMAIN"] %>; upgrade-insecure-requests"; | ||||
|         # add_header Content-Security-Policy "style-src 'self' 'unsafe-inline'; script-src 'self'; object-src 'self'; img-src data: https:; media-src data: https:; connect-src 'self' wss://<%= ENV["LOCAL_DOMAIN"] %>; upgrade-insecure-requests"; | ||||
|  | ||||
|         root /app/public; | ||||
|  | ||||
|         client_max_body_size 8M; | ||||
|         client_max_body_size 80M; | ||||
|  | ||||
|         location / { | ||||
|             try_files $uri @rails; | ||||
|   | ||||
| @@ -10,7 +10,7 @@ | ||||
|     "build:production": "cross-env RAILS_ENV=production NODE_ENV=production ./bin/webpack", | ||||
|     "manage:translations": "node ./config/webpack/translationRunner.js", | ||||
|     "start": "node ./streaming/index.js", | ||||
|     "test": "npm-run-all test:lint test:jest", | ||||
|     "test": "npm run test:lint && npm run test:jest", | ||||
|     "test:lint": "eslint -c .eslintrc.yml --ext=js app/javascript/ config/webpack/ streaming/", | ||||
|     "test:jest": "cross-env NODE_ENV=test jest --coverage" | ||||
|   }, | ||||
| @@ -76,7 +76,6 @@ | ||||
|     "mini-css-extract-plugin": "^0.4.2", | ||||
|     "mkdirp": "^0.5.1", | ||||
|     "node-sass": "^4.9.2", | ||||
|     "npm-run-all": "^4.1.2", | ||||
|     "npmlog": "^4.1.2", | ||||
|     "object-assign": "^4.1.1", | ||||
|     "object-fit-images": "^3.2.3", | ||||
|   | ||||
| @@ -99,10 +99,12 @@ describe AuthorizeInteractionsController do | ||||
|  | ||||
|         allow(ResolveAccountService).to receive(:new).and_return(service) | ||||
|         allow(service).to receive(:call).with('user@hostname').and_return(target_account) | ||||
|         allow(service).to receive(:call).with(target_account, skip_webfinger: true).and_return(target_account) | ||||
|  | ||||
|  | ||||
|         post :create, params: { acct: 'acct:user@hostname' } | ||||
|  | ||||
|         expect(service).to have_received(:call).with('user@hostname') | ||||
|         expect(service).to have_received(:call).with(target_account, skip_webfinger: true) | ||||
|         expect(account.following?(target_account)).to be true | ||||
|         expect(response).to render_template(:success) | ||||
|       end | ||||
|   | ||||
| @@ -48,9 +48,11 @@ describe Request do | ||||
|       end | ||||
|  | ||||
|       it 'executes a HTTP request when the first address is private' do | ||||
|         allow(Addrinfo).to receive(:foreach).with('example.com', nil, nil, :SOCK_STREAM) | ||||
|                                             .and_yield(Addrinfo.new(["AF_INET", 0, "example.com", "0.0.0.0"], :PF_INET, :SOCK_STREAM)) | ||||
|                                             .and_yield(Addrinfo.new(["AF_INET6", 0, "example.com", "2001:4860:4860::8844"], :PF_INET6, :SOCK_STREAM)) | ||||
|         resolver = double | ||||
|  | ||||
|         allow(resolver).to receive(:getaddresses).with('example.com').and_return(%w(0.0.0.0 2001:4860:4860::8844)) | ||||
|         allow(resolver).to receive(:timeouts=).and_return(nil) | ||||
|         allow(Resolv::DNS).to receive(:open).and_yield(resolver) | ||||
|  | ||||
|         expect { |block| subject.perform &block }.to yield_control | ||||
|         expect(a_request(:get, 'http://example.com')).to have_been_made.once | ||||
| @@ -81,9 +83,12 @@ describe Request do | ||||
|       end | ||||
|  | ||||
|       it 'raises Mastodon::ValidationError' do | ||||
|         allow(Addrinfo).to receive(:foreach).with('example.com', nil, nil, :SOCK_STREAM) | ||||
|                                             .and_yield(Addrinfo.new(["AF_INET", 0, "example.com", "0.0.0.0"], :PF_INET, :SOCK_STREAM)) | ||||
|                                             .and_yield(Addrinfo.new(["AF_INET6", 0, "example.com", "2001:db8::face"], :PF_INET6, :SOCK_STREAM)) | ||||
|         resolver = double | ||||
|  | ||||
|         allow(resolver).to receive(:getaddresses).with('example.com').and_return(%w(0.0.0.0 2001:db8::face)) | ||||
|         allow(resolver).to receive(:timeouts=).and_return(nil) | ||||
|         allow(Resolv::DNS).to receive(:open).and_yield(resolver) | ||||
|  | ||||
|         expect { subject.perform }.to raise_error Mastodon::ValidationError | ||||
|       end | ||||
|     end | ||||
|   | ||||
| @@ -618,9 +618,15 @@ RSpec.describe Account, type: :model do | ||||
|         expect(account).not_to model_have_error_on_field(:username) | ||||
|       end | ||||
|  | ||||
|       it 'is invalid if the username doesn\'t only contains letters, numbers and underscores' do | ||||
|       it 'is valid even if the username contains hyphens' do | ||||
|         account = Fabricate.build(:account, domain: 'domain', username: 'the-doctor') | ||||
|         account.valid? | ||||
|         expect(account).to_not model_have_error_on_field(:username) | ||||
|       end | ||||
|  | ||||
|       it 'is invalid if the username doesn\'t only contains letters, numbers, underscores and hyphens' do | ||||
|         account = Fabricate.build(:account, domain: 'domain', username: 'the doctor') | ||||
|         account.valid? | ||||
|         expect(account).to model_have_error_on_field(:username) | ||||
|       end | ||||
|  | ||||
|   | ||||
| @@ -101,7 +101,7 @@ RSpec.describe Notification, type: :model do | ||||
|       before do | ||||
|         allow(accounts_with_ids).to receive(:[]).with(stale_account1.id).and_return(account1) | ||||
|         allow(accounts_with_ids).to receive(:[]).with(stale_account2.id).and_return(account2) | ||||
|         allow(Account).to receive_message_chain(:where, :map, :to_h).and_return(accounts_with_ids) | ||||
|         allow(Account).to receive_message_chain(:where, :each_with_object).and_return(accounts_with_ids) | ||||
|       end | ||||
|  | ||||
|       let(:cached_items) do | ||||
|   | ||||
| @@ -60,8 +60,15 @@ RSpec.describe FetchAtomService, type: :service do | ||||
|         it { is_expected.to eq [url, { :prefetched_body => "" }, :ostatus] } | ||||
|       end | ||||
|  | ||||
|       context 'content_type is json' do | ||||
|         let(:content_type) { 'application/activity+json' } | ||||
|       context 'content_type is activity+json' do | ||||
|         let(:content_type) { 'application/activity+json; charset=utf-8' } | ||||
|         let(:body) { json } | ||||
|  | ||||
|         it { is_expected.to eq [1, { prefetched_body: body, id: true }, :activitypub] } | ||||
|       end | ||||
|  | ||||
|       context 'content_type is ld+json with profile' do | ||||
|         let(:content_type) { 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"' } | ||||
|         let(:body) { json } | ||||
|  | ||||
|         it { is_expected.to eq [1, { prefetched_body: body, id: true }, :activitypub] } | ||||
|   | ||||
							
								
								
									
										141
									
								
								yarn.lock
									
									
									
									
									
								
							
							
						
						
									
										141
									
								
								yarn.lock
									
									
									
									
									
								
							| @@ -1109,11 +1109,6 @@ array-equal@^1.0.0: | ||||
|   resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93" | ||||
|   integrity sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM= | ||||
|  | ||||
| array-filter@~0.0.0: | ||||
|   version "0.0.1" | ||||
|   resolved "https://registry.yarnpkg.com/array-filter/-/array-filter-0.0.1.tgz#7da8cf2e26628ed732803581fd21f67cacd2eeec" | ||||
|   integrity sha1-fajPLiZijtcygDWB/SH2fKzS7uw= | ||||
|  | ||||
| array-find-index@^1.0.1: | ||||
|   version "1.0.2" | ||||
|   resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" | ||||
| @@ -1137,16 +1132,6 @@ array-includes@^3.0.3: | ||||
|     define-properties "^1.1.2" | ||||
|     es-abstract "^1.7.0" | ||||
|  | ||||
| array-map@~0.0.0: | ||||
|   version "0.0.0" | ||||
|   resolved "https://registry.yarnpkg.com/array-map/-/array-map-0.0.0.tgz#88a2bab73d1cf7bcd5c1b118a003f66f665fa662" | ||||
|   integrity sha1-iKK6tz0c97zVwbEYoAP2b2ZfpmI= | ||||
|  | ||||
| array-reduce@~0.0.0: | ||||
|   version "0.0.0" | ||||
|   resolved "https://registry.yarnpkg.com/array-reduce/-/array-reduce-0.0.0.tgz#173899d3ffd1c7d9383e4479525dbe278cab5f2b" | ||||
|   integrity sha1-FziZ0//Rx9k4PkR5Ul2+J4yrXys= | ||||
|  | ||||
| array-union@^1.0.1: | ||||
|   version "1.0.2" | ||||
|   resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" | ||||
| @@ -2411,7 +2396,7 @@ cross-spawn@^5.0.1, cross-spawn@^5.1.0: | ||||
|     shebang-command "^1.2.0" | ||||
|     which "^1.2.9" | ||||
|  | ||||
| cross-spawn@^6.0.0, cross-spawn@^6.0.4, cross-spawn@^6.0.5: | ||||
| cross-spawn@^6.0.0, cross-spawn@^6.0.5: | ||||
|   version "6.0.5" | ||||
|   resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" | ||||
|   integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== | ||||
| @@ -2920,7 +2905,7 @@ double-ended-queue@^2.1.0-0: | ||||
|   resolved "https://registry.yarnpkg.com/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz#103d3527fd31528f40188130c841efdd78264e5c" | ||||
|   integrity sha1-ED01J/0xUo9AGIEwyEHv3XgmTlw= | ||||
|  | ||||
| duplexer@^0.1.1, duplexer@~0.1.1: | ||||
| duplexer@^0.1.1: | ||||
|   version "0.1.1" | ||||
|   resolved "http://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1" | ||||
|   integrity sha1-rOb/gIwc5mtX0ev5eXessCM0z8E= | ||||
| @@ -3079,7 +3064,7 @@ error-ex@^1.2.0, error-ex@^1.3.1: | ||||
|   dependencies: | ||||
|     is-arrayish "^0.2.1" | ||||
|  | ||||
| es-abstract@^1.10.0, es-abstract@^1.4.3, es-abstract@^1.5.0, es-abstract@^1.5.1, es-abstract@^1.6.1, es-abstract@^1.7.0: | ||||
| es-abstract@^1.10.0, es-abstract@^1.5.0, es-abstract@^1.5.1, es-abstract@^1.6.1, es-abstract@^1.7.0: | ||||
|   version "1.12.0" | ||||
|   resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.12.0.tgz#9dbbdd27c6856f0001421ca18782d786bf8a6165" | ||||
|   integrity sha512-C8Fx/0jFmV5IPoMOFPA9P9G5NtqW+4cOPit3MIuvR2t7Ag2K15EJTpxnHAYTzL+aYQJIESYeXZmDBfOBE1HcpA== | ||||
| @@ -3329,20 +3314,6 @@ etag@~1.8.1: | ||||
|   resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" | ||||
|   integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= | ||||
|  | ||||
| event-stream@~3.3.0: | ||||
|   version "3.3.6" | ||||
|   resolved "https://registry.yarnpkg.com/event-stream/-/event-stream-3.3.6.tgz#cac1230890e07e73ec9cacd038f60a5b66173eef" | ||||
|   integrity sha512-dGXNg4F/FgVzlApjzItL+7naHutA3fDqbV/zAZqDDlXTjiMnQmZKu+prImWKszeBM5UQeGvAl3u1wBiKeDh61g== | ||||
|   dependencies: | ||||
|     duplexer "^0.1.1" | ||||
|     flatmap-stream "^0.1.0" | ||||
|     from "^0.1.7" | ||||
|     map-stream "0.0.7" | ||||
|     pause-stream "^0.0.11" | ||||
|     split "^1.0.1" | ||||
|     stream-combiner "^0.2.2" | ||||
|     through "^2.3.8" | ||||
|  | ||||
| eventemitter3@^3.0.0: | ||||
|   version "3.1.0" | ||||
|   resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.0.tgz#090b4d6cdbd645ed10bf750d4b5407942d7ba163" | ||||
| @@ -3744,11 +3715,6 @@ flat-cache@^1.2.1: | ||||
|     graceful-fs "^4.1.2" | ||||
|     write "^0.2.1" | ||||
|  | ||||
| flatmap-stream@^0.1.0: | ||||
|   version "0.1.0" | ||||
|   resolved "https://registry.yarnpkg.com/flatmap-stream/-/flatmap-stream-0.1.0.tgz#ed54e01422cd29281800914fcb968d58b685d5f1" | ||||
|   integrity sha512-Nlic4ZRYxikqnK5rj3YoxDVKGGtUjcNDUtvQ7XsdGLZmMwdUYnXf10o1zcXtzEZTBgc6GxeRpQxV/Wu3WPIIHA== | ||||
|  | ||||
| flatten@^1.0.2: | ||||
|   version "1.0.2" | ||||
|   resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782" | ||||
| @@ -3846,11 +3812,6 @@ from2@^2.1.0: | ||||
|     inherits "^2.0.1" | ||||
|     readable-stream "^2.0.0" | ||||
|  | ||||
| from@^0.1.7: | ||||
|   version "0.1.7" | ||||
|   resolved "https://registry.yarnpkg.com/from/-/from-0.1.7.tgz#83c60afc58b9c56997007ed1a768b3ab303a44fe" | ||||
|   integrity sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4= | ||||
|  | ||||
| fs-extra@^7.0.0: | ||||
|   version "7.0.0" | ||||
|   resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.0.tgz#8cc3f47ce07ef7b3593a11b9fb245f7e34c041d6" | ||||
| @@ -5642,16 +5603,6 @@ load-json-file@^2.0.0: | ||||
|     pify "^2.0.0" | ||||
|     strip-bom "^3.0.0" | ||||
|  | ||||
| load-json-file@^4.0.0: | ||||
|   version "4.0.0" | ||||
|   resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" | ||||
|   integrity sha1-L19Fq5HjMhYjT9U62rZo607AmTs= | ||||
|   dependencies: | ||||
|     graceful-fs "^4.1.2" | ||||
|     parse-json "^4.0.0" | ||||
|     pify "^3.0.0" | ||||
|     strip-bom "^3.0.0" | ||||
|  | ||||
| loader-runner@^2.3.0: | ||||
|   version "2.3.0" | ||||
|   resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.3.0.tgz#f482aea82d543e07921700d5a46ef26fdac6b8a2" | ||||
| @@ -5841,11 +5792,6 @@ map-obj@^1.0.0, map-obj@^1.0.1: | ||||
|   resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" | ||||
|   integrity sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0= | ||||
|  | ||||
| map-stream@0.0.7: | ||||
|   version "0.0.7" | ||||
|   resolved "https://registry.yarnpkg.com/map-stream/-/map-stream-0.0.7.tgz#8a1f07896d82b10926bd3744a2420009f88974a8" | ||||
|   integrity sha1-ih8HiW2CsQkmvTdEokIACfiJdKg= | ||||
|  | ||||
| map-visit@^1.0.0: | ||||
|   version "1.0.0" | ||||
|   resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" | ||||
| @@ -5905,11 +5851,6 @@ memory-fs@^0.4.0, memory-fs@~0.4.1: | ||||
|     errno "^0.1.3" | ||||
|     readable-stream "^2.0.1" | ||||
|  | ||||
| memorystream@^0.3.1: | ||||
|   version "0.3.1" | ||||
|   resolved "https://registry.yarnpkg.com/memorystream/-/memorystream-0.3.1.tgz#86d7090b30ce455d63fbae12dda51a47ddcaf9b2" | ||||
|   integrity sha1-htcJCzDORV1j+64S3aUaR93K+bI= | ||||
|  | ||||
| meow@^3.7.0: | ||||
|   version "3.7.0" | ||||
|   resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" | ||||
| @@ -6462,21 +6403,6 @@ npm-packlist@^1.1.6: | ||||
|     ignore-walk "^3.0.1" | ||||
|     npm-bundled "^1.0.1" | ||||
|  | ||||
| npm-run-all@^4.1.2: | ||||
|   version "4.1.3" | ||||
|   resolved "https://registry.yarnpkg.com/npm-run-all/-/npm-run-all-4.1.3.tgz#49f15b55a66bb4101664ce270cb18e7103f8f185" | ||||
|   integrity sha512-aOG0N3Eo/WW+q6sUIdzcV2COS8VnTZCmdji0VQIAZF3b+a3YWb0AD0vFIyjKec18A7beLGbaQ5jFTNI2bPt9Cg== | ||||
|   dependencies: | ||||
|     ansi-styles "^3.2.0" | ||||
|     chalk "^2.1.0" | ||||
|     cross-spawn "^6.0.4" | ||||
|     memorystream "^0.3.1" | ||||
|     minimatch "^3.0.4" | ||||
|     ps-tree "^1.1.0" | ||||
|     read-pkg "^3.0.0" | ||||
|     shell-quote "^1.6.1" | ||||
|     string.prototype.padend "^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" | ||||
| @@ -6979,20 +6905,6 @@ path-type@^2.0.0: | ||||
|   dependencies: | ||||
|     pify "^2.0.0" | ||||
|  | ||||
| path-type@^3.0.0: | ||||
|   version "3.0.0" | ||||
|   resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" | ||||
|   integrity sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg== | ||||
|   dependencies: | ||||
|     pify "^3.0.0" | ||||
|  | ||||
| pause-stream@^0.0.11: | ||||
|   version "0.0.11" | ||||
|   resolved "http://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz#fe5a34b0cbce12b5aa6a2b403ee2e73b602f1445" | ||||
|   integrity sha1-/lo0sMvOErWqaitAPuLnO2AvFEU= | ||||
|   dependencies: | ||||
|     through "~2.3" | ||||
|  | ||||
| pbkdf2@^3.0.3: | ||||
|   version "3.0.16" | ||||
|   resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.16.tgz#7404208ec6b01b62d85bf83853a8064f8d9c2a5c" | ||||
| @@ -7663,13 +7575,6 @@ prr@~1.0.1: | ||||
|   resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" | ||||
|   integrity sha1-0/wRS6BplaRexok/SEzrHXj19HY= | ||||
|  | ||||
| ps-tree@^1.1.0: | ||||
|   version "1.1.0" | ||||
|   resolved "https://registry.yarnpkg.com/ps-tree/-/ps-tree-1.1.0.tgz#b421b24140d6203f1ed3c76996b4427b08e8c014" | ||||
|   integrity sha1-tCGyQUDWID8e08dplrRCewjowBQ= | ||||
|   dependencies: | ||||
|     event-stream "~3.3.0" | ||||
|  | ||||
| pseudomap@^1.0.2: | ||||
|   version "1.0.2" | ||||
|   resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" | ||||
| @@ -8115,15 +8020,6 @@ read-pkg@^2.0.0: | ||||
|     normalize-package-data "^2.3.2" | ||||
|     path-type "^2.0.0" | ||||
|  | ||||
| read-pkg@^3.0.0: | ||||
|   version "3.0.0" | ||||
|   resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389" | ||||
|   integrity sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k= | ||||
|   dependencies: | ||||
|     load-json-file "^4.0.0" | ||||
|     normalize-package-data "^2.3.2" | ||||
|     path-type "^3.0.0" | ||||
|  | ||||
| "readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.2.9, readable-stream@^2.3.3, readable-stream@^2.3.6: | ||||
|   version "2.3.6" | ||||
|   resolved "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" | ||||
| @@ -8827,16 +8723,6 @@ shebang-regex@^1.0.0: | ||||
|   resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" | ||||
|   integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= | ||||
|  | ||||
| shell-quote@^1.6.1: | ||||
|   version "1.6.1" | ||||
|   resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.6.1.tgz#f4781949cce402697127430ea3b3c5476f481767" | ||||
|   integrity sha1-9HgZSczkAmlxJ0MOo7PFR29IF2c= | ||||
|   dependencies: | ||||
|     array-filter "~0.0.0" | ||||
|     array-map "~0.0.0" | ||||
|     array-reduce "~0.0.0" | ||||
|     jsonify "~0.0.0" | ||||
|  | ||||
| shellwords@^0.1.1: | ||||
|   version "0.1.1" | ||||
|   resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" | ||||
| @@ -9039,7 +8925,7 @@ split-string@^3.0.1, split-string@^3.0.2: | ||||
|   dependencies: | ||||
|     extend-shallow "^3.0.0" | ||||
|  | ||||
| split@^1.0.0, split@^1.0.1: | ||||
| split@^1.0.0: | ||||
|   version "1.0.1" | ||||
|   resolved "https://registry.yarnpkg.com/split/-/split-1.0.1.tgz#605bd9be303aa59fb35f9229fbea0ddec9ea07d9" | ||||
|   integrity sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg== | ||||
| @@ -9124,14 +9010,6 @@ stream-browserify@^2.0.1: | ||||
|     inherits "~2.0.1" | ||||
|     readable-stream "^2.0.2" | ||||
|  | ||||
| stream-combiner@^0.2.2: | ||||
|   version "0.2.2" | ||||
|   resolved "http://registry.npmjs.org/stream-combiner/-/stream-combiner-0.2.2.tgz#aec8cbac177b56b6f4fa479ced8c1912cee52858" | ||||
|   integrity sha1-rsjLrBd7Vrb0+kec7YwZEs7lKFg= | ||||
|   dependencies: | ||||
|     duplexer "~0.1.1" | ||||
|     through "~2.3.4" | ||||
|  | ||||
| stream-each@^1.1.0: | ||||
|   version "1.2.3" | ||||
|   resolved "https://registry.yarnpkg.com/stream-each/-/stream-each-1.2.3.tgz#ebe27a0c389b04fbcc233642952e10731afa9bae" | ||||
| @@ -9181,15 +9059,6 @@ string-width@^1.0.1, string-width@^1.0.2: | ||||
|     is-fullwidth-code-point "^2.0.0" | ||||
|     strip-ansi "^4.0.0" | ||||
|  | ||||
| string.prototype.padend@^3.0.0: | ||||
|   version "3.0.0" | ||||
|   resolved "https://registry.yarnpkg.com/string.prototype.padend/-/string.prototype.padend-3.0.0.tgz#f3aaef7c1719f170c5eab1c32bf780d96e21f2f0" | ||||
|   integrity sha1-86rvfBcZ8XDF6rHDK/eA2W4h8vA= | ||||
|   dependencies: | ||||
|     define-properties "^1.1.2" | ||||
|     es-abstract "^1.4.3" | ||||
|     function-bind "^1.0.2" | ||||
|  | ||||
| string.prototype.trim@^1.1.2: | ||||
|   version "1.1.2" | ||||
|   resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.1.2.tgz#d04de2c89e137f4d7d206f086b5ed2fae6be8cea" | ||||
| @@ -9407,7 +9276,7 @@ through2@^2.0.0: | ||||
|     readable-stream "^2.1.5" | ||||
|     xtend "~4.0.1" | ||||
|  | ||||
| through@2, through@^2.3.6, through@^2.3.8, through@~2.3, through@~2.3.4: | ||||
| through@2, through@^2.3.6: | ||||
|   version "2.3.8" | ||||
|   resolved "http://registry.npmjs.org/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" | ||||
|   integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= | ||||
|   | ||||
		Reference in New Issue
	
	Block a user