diff --git a/cli/clean-cached-remote-files.js b/cli/clean-cached-remote-files.js
index e4db37ef97..a9c38a4cdf 100644
--- a/cli/clean-cached-remote-files.js
+++ b/cli/clean-cached-remote-files.js
@@ -8,7 +8,8 @@ const { default: User } = require('../built/models/user');
 const q = {
 	'metadata._user.host': {
 		$ne: null
-	}
+	},
+	'metadata.isMetaOnly': false
 };
 
 async function main() {
@@ -56,8 +57,7 @@ async function main() {
 
 					DriveFile.update({ _id: file._id }, {
 						$set: {
-							'metadata.deletedAt': new Date(),
-							'metadata.isExpired': true
+							'metadata.isMetaOnly': true
 						}
 					})
 				]).then(async () => {
diff --git a/locales/ja.yml b/locales/ja.yml
index 7d818193b1..49046d26e0 100644
--- a/locales/ja.yml
+++ b/locales/ja.yml
@@ -855,6 +855,8 @@ mobile/views/pages/settings.vue:
   behavior: "動作"
   fetch-on-scroll: "スクロールで自動読み込み"
   disable-via-mobile: "「モバイルからの投稿」フラグを付けない"
+  load-raw-images: "添付された画像を高画質で表示する"
+  load-remote-media: "リモートサーバーのメディアを表示する"
   twitter: "Twitter連携"
   twitter-connect: "Twitterアカウントに接続する"
   twitter-reconnect: "再接続する"
diff --git a/src/client/app/mobile/views/components/media-image.vue b/src/client/app/mobile/views/components/media-image.vue
index c4622b01a7..c2f9c66e84 100644
--- a/src/client/app/mobile/views/components/media-image.vue
+++ b/src/client/app/mobile/views/components/media-image.vue
@@ -16,13 +16,18 @@ export default Vue.extend({
 		}
 	},
 	computed: {
-		lightmode(): boolean {
-			return this.$store.state.device.lightmode;
-		},
 		style(): any {
+			let url = `url(${this.image.url}?thumbnail)`;
+
+			if (this.$store.state.device.loadRemoteMedia || this.$store.state.device.lightmode) {
+				url = null;
+			} else if (this.raw || this.$store.state.device.loadRawImages) {
+				url = `url(${this.image.url})`;
+			}
+
 			return {
 				'background-color': this.image.properties.avgColor && this.image.properties.avgColor.length == 3 ? `rgb(${this.image.properties.avgColor.join(',')})` : 'transparent',
-				'background-image': this.lightmode ? null : this.raw ? `url(${this.image.url})` : `url(${this.image.url}?thumbnail&size=512)`
+				'background-image': url
 			};
 		}
 	}
diff --git a/src/client/app/mobile/views/pages/settings.vue b/src/client/app/mobile/views/pages/settings.vue
index 270c9f4055..3bb25f88f8 100644
--- a/src/client/app/mobile/views/pages/settings.vue
+++ b/src/client/app/mobile/views/pages/settings.vue
@@ -59,6 +59,14 @@
 						<md-switch v-model="clientSettings.disableViaMobile" @change="onChangeDisableViaMobile">%i18n:@disable-via-mobile%</md-switch>
 					</div>
 
+					<div>
+						<md-switch v-model="loadRawImages">%i18n:@load-raw-images%</md-switch>
+					</div>
+
+					<div>
+						<md-switch v-model="clientSettings.loadRemoteMedia" @change="onChangeLoadRemoteMedia">%i18n:@load-remote-media%</md-switch>
+					</div>
+
 					<div>
 						<md-switch v-model="lightmode">%i18n:@i-am-under-limited-internet%</md-switch>
 					</div>
@@ -166,6 +174,11 @@ export default Vue.extend({
 			set(value) { this.$store.commit('device/set', { key: 'lightmode', value }); }
 		},
 
+		loadRawImages: {
+			get() { return this.$store.state.device.loadRawImages; },
+			set(value) { this.$store.commit('device/set', { key: 'loadRawImages', value }); }
+		},
+
 		lang: {
 			get() { return this.$store.state.device.lang; },
 			set(value) { this.$store.commit('device/set', { key: 'lang', value }); }
@@ -195,6 +208,13 @@ export default Vue.extend({
 			});
 		},
 
+		onChangeLoadRemoteMedia(v) {
+			this.$store.dispatch('settings/set', {
+				key: 'loadRemoteMedia',
+				value: v
+			});
+		},
+
 		onChangeCircleIcons(v) {
 			this.$store.dispatch('settings/set', {
 				key: 'circleIcons',
diff --git a/src/client/app/store.ts b/src/client/app/store.ts
index 74a5852c1a..e300d31d8d 100644
--- a/src/client/app/store.ts
+++ b/src/client/app/store.ts
@@ -13,7 +13,9 @@ const defaultSettings = {
 	gradientWindowHeader: false,
 	showReplyTarget: true,
 	showMyRenotes: true,
-	showRenotedMyNotes: true
+	showRenotedMyNotes: true,
+	loadRemoteMedia: true,
+	disableViaMobile: false
 };
 
 const defaultDeviceSettings = {
@@ -26,6 +28,7 @@ const defaultDeviceSettings = {
 	preventUpdate: false,
 	debug: false,
 	lightmode: false,
+	loadRawImages: false,
 	postStyle: 'standard'
 };
 
diff --git a/src/config/types.ts b/src/config/types.ts
index dff3f7d37c..910c03c2c1 100644
--- a/src/config/types.ts
+++ b/src/config/types.ts
@@ -41,6 +41,8 @@ export type Source = {
 		secret_key: string;
 	};
 
+	preventCacheRemoteFiles: boolean;
+
 	/**
 	 * ゴーストアカウントのID
 	 */
diff --git a/src/models/drive-file.ts b/src/models/drive-file.ts
index 8a18567dc6..a3a567038e 100644
--- a/src/models/drive-file.ts
+++ b/src/models/drive-file.ts
@@ -32,7 +32,7 @@ export type IMetadata = {
 	uri?: string;
 	url?: string;
 	deletedAt?: Date;
-	isExpired?: boolean;
+	isMetaOnly?: boolean;
 };
 
 export type IDriveFile = {
@@ -155,7 +155,8 @@ export const pack = (
 	_target = Object.assign(_target, _file.metadata);
 
 	_target.src = _file.metadata.url;
-	_target.url = `${config.drive_url}/${_target.id}/${encodeURIComponent(_target.name)}`;
+	_target.url = _file.metadata.isMetaOnly ? _file.metadata.url : `${config.drive_url}/${_target.id}/${encodeURIComponent(_target.name)}`;
+	_target.isRemote = _file.metadata.isMetaOnly;
 
 	if (_target.properties == null) _target.properties = {};
 
diff --git a/src/server/file/send-drive-file.ts b/src/server/file/send-drive-file.ts
index d613a3aa5f..e04400317f 100644
--- a/src/server/file/send-drive-file.ts
+++ b/src/server/file/send-drive-file.ts
@@ -33,11 +33,12 @@ export default async function(ctx: Koa.Context) {
 
 	if (file.metadata.deletedAt) {
 		ctx.status = 410;
-		if (file.metadata.isExpired) {
-			await send(ctx, '/cache-expired.png', { root: assets });
-		} else {
-			await send(ctx, '/tombstone.png', { root: assets });
-		}
+		await send(ctx, '/tombstone.png', { root: assets });
+		return;
+	}
+
+	if (file.metadata.isMetaOnly) {
+		ctx.status = 204;
 		return;
 	}
 
diff --git a/src/services/drive/add-file.ts b/src/services/drive/add-file.ts
index 8b1d3eef00..413c914171 100644
--- a/src/services/drive/add-file.ts
+++ b/src/services/drive/add-file.ts
@@ -104,6 +104,7 @@ export default async function(
 	comment: string = null,
 	folderId: mongodb.ObjectID = null,
 	force: boolean = false,
+	metaOnly: boolean = false,
 	url: string = null,
 	uri: string = null
 ): Promise<IDriveFile> {
@@ -170,38 +171,40 @@ export default async function(
 	}
 
 	//#region Check drive usage
-	const usage = await DriveFile
-		.aggregate([{
-			$match: {
-				'metadata.userId': user._id,
-				'metadata.deletedAt': { $exists: false }
-			}
-		}, {
-			$project: {
-				length: true
-			}
-		}, {
-			$group: {
-				_id: null,
-				usage: { $sum: '$length' }
-			}
-		}])
-		.then((aggregates: any[]) => {
-			if (aggregates.length > 0) {
-				return aggregates[0].usage;
-			}
-			return 0;
-		});
+	if (!metaOnly) {
+		const usage = await DriveFile
+			.aggregate([{
+				$match: {
+					'metadata.userId': user._id,
+					'metadata.deletedAt': { $exists: false }
+				}
+			}, {
+				$project: {
+					length: true
+				}
+			}, {
+				$group: {
+					_id: null,
+					usage: { $sum: '$length' }
+				}
+			}])
+			.then((aggregates: any[]) => {
+				if (aggregates.length > 0) {
+					return aggregates[0].usage;
+				}
+				return 0;
+			});
 
-	log(`drive usage is ${usage}`);
+		log(`drive usage is ${usage}`);
 
-	// If usage limit exceeded
-	if (usage + size > user.driveCapacity) {
-		if (isLocalUser(user)) {
-			throw 'no-free-space';
-		} else {
-			// (アバターまたはバナーを含まず)最も古いファイルを削除する
-			deleteOldFile(user);
+		// If usage limit exceeded
+		if (usage + size > user.driveCapacity) {
+			if (isLocalUser(user)) {
+				throw 'no-free-space';
+			} else {
+				// (アバターまたはバナーを含まず)最も古いファイルを削除する
+				deleteOldFile(user);
+			}
 		}
 	}
 	//#endregion
@@ -270,8 +273,6 @@ export default async function(
 
 	const [folder] = await Promise.all([fetchFolder(), propPromises]);
 
-	const readable = fs.createReadStream(path);
-
 	const metadata = {
 		userId: user._id,
 		_user: {
@@ -279,7 +280,8 @@ export default async function(
 		},
 		folderId: folder !== null ? folder._id : null,
 		comment: comment,
-		properties: properties
+		properties: properties,
+		isMetaOnly: metaOnly
 	} as IMetadata;
 
 	if (url !== null) {
@@ -290,7 +292,16 @@ export default async function(
 		metadata.uri = uri;
 	}
 
-	const driveFile = await (writeChunks(detectedName, readable, mime, metadata) as Promise<IDriveFile>);
+	const driveFile = metaOnly
+		? await DriveFile.insert({
+			length: 0,
+			uploadDate: new Date(),
+			md5: hash,
+			filename: detectedName,
+			metadata: metadata,
+			contentType: mime
+		})
+		: await (writeChunks(detectedName, fs.createReadStream(path), mime, metadata) as Promise<IDriveFile>);
 
 	log(`drive file has been created ${driveFile._id}`);
 
@@ -300,13 +311,15 @@ export default async function(
 		publishDriveStream(user._id, 'file_created', packedFile);
 	});
 
-	try {
-		const thumb = await genThumbnail(driveFile);
-		if (thumb) {
-			await writeThumbnailChunks(detectedName, thumb, driveFile._id);
+	if (!metaOnly) {
+		try {
+			const thumb = await genThumbnail(driveFile);
+			if (thumb) {
+				await writeThumbnailChunks(detectedName, thumb, driveFile._id);
+			}
+		} catch (e) {
+			// noop
 		}
-	} catch (e) {
-		// noop
 	}
 
 	return driveFile;
diff --git a/src/services/drive/upload-from-url.ts b/src/services/drive/upload-from-url.ts
index ad2620c036..e216ca603d 100644
--- a/src/services/drive/upload-from-url.ts
+++ b/src/services/drive/upload-from-url.ts
@@ -1,14 +1,17 @@
+import * as fs from 'fs';
 import * as URL from 'url';
-import { IDriveFile, validateFileName } from '../../models/drive-file';
-import create from './add-file';
+
 import * as debug from 'debug';
 import * as tmp from 'tmp';
-import * as fs from 'fs';
 import * as request from 'request';
 
+import { IDriveFile, validateFileName } from '../../models/drive-file';
+import create from './add-file';
+import config from '../../config';
+
 const log = debug('misskey:drive:upload-from-url');
 
-export default async (url, user, folderId = null, uri = null): Promise<IDriveFile> => {
+export default async (url: string, user, folderId = null, uri: string = null): Promise<IDriveFile> => {
 	log(`REQUESTED: ${url}`);
 
 	let name = URL.parse(url).pathname.split('/').pop();
@@ -43,7 +46,7 @@ export default async (url, user, folderId = null, uri = null): Promise<IDriveFil
 	let error;
 
 	try {
-		driveFile = await create(user, path, name, null, folderId, false, url, uri);
+		driveFile = await create(user, path, name, null, folderId, false, config.preventCacheRemoteFiles, url, uri);
 		log(`created: ${driveFile._id}`);
 	} catch (e) {
 		error = e;