diff --git a/core/modules/field/tests/src/Kernel/EntityReference/EntityReferenceItemTest.php b/core/modules/field/tests/src/Kernel/EntityReference/EntityReferenceItemTest.php
index edb3029..574a4aa 100644
--- a/core/modules/field/tests/src/Kernel/EntityReference/EntityReferenceItemTest.php
+++ b/core/modules/field/tests/src/Kernel/EntityReference/EntityReferenceItemTest.php
@@ -514,6 +514,7 @@ public function testAutocreateValidation() {
     $file = File::create([
       'filename' => $filename,
       'status' => 0,
+      'uid' => 1,
     ]);
 
     $entity = EntityTest::create([
diff --git a/core/modules/file/src/Entity/File.php b/core/modules/file/src/Entity/File.php
index da6d7c5..b094ff5 100644
--- a/core/modules/file/src/Entity/File.php
+++ b/core/modules/file/src/Entity/File.php
@@ -232,6 +232,7 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
     $fields['uid'] = BaseFieldDefinition::create('entity_reference')
       ->setLabel(t('User ID'))
       ->setDescription(t('The user ID of the file.'))
+      ->setDefaultValueCallback('Drupal\file\Entity\File::getCurrentUserId')
       ->setSetting('target_type', 'user');
 
     $fields['filename'] = BaseFieldDefinition::create('string')
@@ -272,4 +273,16 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
     return $fields;
   }
 
+  /**
+   * Default value callback for 'uid' base field definition.
+   *
+   * @see \Drupal\file\Entity\File::baseFieldDefinitions::baseFieldDefinitions().
+   *
+   * @return array
+   *   An array of default values.
+   */
+  public static function getCurrentUserId() {
+    return array(\Drupal::currentUser()->id());
+  }
+
 }
diff --git a/core/modules/file/src/FileAccessControlHandler.php b/core/modules/file/src/FileAccessControlHandler.php
index f9569ec..c53b1f8 100644
--- a/core/modules/file/src/FileAccessControlHandler.php
+++ b/core/modules/file/src/FileAccessControlHandler.php
@@ -6,6 +6,8 @@
 use Drupal\Core\Entity\EntityAccessControlHandler;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityStorageInterface;
+use Drupal\Core\Field\FieldDefinitionInterface;
+use Drupal\Core\Field\FieldItemListInterface;
 use Drupal\Core\Session\AccountInterface;
 
 /**
@@ -43,6 +45,16 @@ protected function checkAccess(EntityInterface $entity, $operation, AccountInter
       }
     }
 
+    if ($operation == 'delete' || $operation == 'update') {
+      $account = $this->prepareUser($account);
+      $file_uid = $entity->get('uid')->getValue();
+      // Only admin users and the file owner can delete and update the file entity.
+      if ($account->hasPermission('administer nodes') || $account->id() == $file_uid[0]['target_id']) {
+        return AccessResult::allowed()->addCacheableDependency($account);
+      }
+      return AccessResult::forbidden();
+    }
+
     // No opinion.
     return AccessResult::neutral();
   }
@@ -63,4 +75,25 @@ protected function getFileReferences(FileInterface $file) {
     return file_get_file_references($file, NULL, EntityStorageInterface::FIELD_LOAD_REVISION, NULL);
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  protected function checkFieldAccess($operation, FieldDefinitionInterface $field_definition, AccountInterface $account, FieldItemListInterface $items = NULL) {
+    // No user can edit the status of a file. Prevents saving a new file as
+    // persistent before even validating it.
+    if ($field_definition->getName() === 'status' && $operation === 'edit') {
+      return AccessResult::forbidden();
+    }
+    return parent::checkFieldAccess($operation, $field_definition, $account, $items);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) {
+    // @todo Remove this override after https://www.drupal.org/node/2310307 is
+    // fixed.
+    return AccessResult::allowed();
+  }
+
 }
diff --git a/core/modules/hal/hal.services.yml b/core/modules/hal/hal.services.yml
index e817fbf..80cce6d 100644
--- a/core/modules/hal/hal.services.yml
+++ b/core/modules/hal/hal.services.yml
@@ -16,7 +16,7 @@ services:
     class: Drupal\hal\Normalizer\FileEntityNormalizer
     tags:
       - { name: normalizer, priority: 20 }
-    arguments: ['@entity.manager', '@http_client', '@rest.link_manager', '@module_handler']
+    arguments: ['@entity.manager', '@rest.link_manager', '@module_handler', '@file_system']
   serializer.normalizer.entity.hal:
     class: Drupal\hal\Normalizer\ContentEntityNormalizer
     arguments: ['@rest.link_manager', '@entity.manager', '@module_handler']
diff --git a/core/modules/hal/src/Normalizer/ContentEntityNormalizer.php b/core/modules/hal/src/Normalizer/ContentEntityNormalizer.php
index fd8158b..6e43efa 100644
--- a/core/modules/hal/src/Normalizer/ContentEntityNormalizer.php
+++ b/core/modules/hal/src/Normalizer/ContentEntityNormalizer.php
@@ -126,8 +126,9 @@ public function denormalize($data, $class, $format = NULL, array $context = arra
       throw new UnexpectedValueException('The type link relation must be specified.');
     }
 
-    // Create the entity.
+    // Identify entity type and bundle.
     $typed_data_ids = $this->getTypedDataIds($data['_links']['type'], $context);
+    // Get definition of entity type and bundle (if any).
     $entity_type = $this->entityManager->getDefinition($typed_data_ids['entity_type']);
     $langcode_key = $entity_type->getKey('langcode');
     $values = array();
@@ -146,6 +147,7 @@ public function denormalize($data, $class, $format = NULL, array $context = arra
       unset($data[$bundle_key]);
     }
 
+    // Create the entity.
     $entity = $this->entityManager->getStorage($typed_data_ids['entity_type'])->create($values);
 
     // Remove links from data array.
diff --git a/core/modules/hal/src/Normalizer/EntityReferenceItemNormalizer.php b/core/modules/hal/src/Normalizer/EntityReferenceItemNormalizer.php
index 6c73256..7017575 100644
--- a/core/modules/hal/src/Normalizer/EntityReferenceItemNormalizer.php
+++ b/core/modules/hal/src/Normalizer/EntityReferenceItemNormalizer.php
@@ -3,6 +3,7 @@
 namespace Drupal\hal\Normalizer;
 
 use Drupal\Core\Entity\FieldableEntityInterface;
+use Drupal\Core\Field\FieldItemInterface;
 use Drupal\rest\LinkManager\LinkManagerInterface;
 use Drupal\serialization\EntityResolver\EntityResolverInterface;
 use Drupal\serialization\EntityResolver\UuidReferenceInterface;
@@ -53,12 +54,19 @@ public function normalize($field_item, $format = NULL, array $context = array())
     /** @var $field_item \Drupal\Core\Field\FieldItemInterface */
     $target_entity = $field_item->get('entity')->getValue();
 
+    // Parent implementation simply copies the properties.
+    $properties = parent::normalize($field_item, $format, $context);
+
     // If this is not a content entity, let the parent implementation handle it,
     // only content entities are supported as embedded resources.
     if (!($target_entity instanceof FieldableEntityInterface)) {
-      return parent::normalize($field_item, $format, $context);
+      return $properties;
     }
 
+    // Discard the target_id property.
+    $field_name = $field_item->getParent()->getName();
+    unset($properties[$field_name][0]['target_id']);
+
     // If the parent entity passed in a langcode, unset it before normalizing
     // the target entity. Otherwise, untranslatable fields of the target entity
     // will include the langcode.
@@ -74,13 +82,13 @@ public function normalize($field_item, $format = NULL, array $context = array())
     if ($langcode) {
       $embedded['lang'] = $link['lang'] = $langcode;
     }
+    // Merge in the properties of the parent implementation.
+    $embedded += $properties[$field_name][0];
 
     // The returned structure will be recursively merged into the normalized
     // entity so that the items are properly added to the _links and _embedded
     // objects.
-    $field_name = $field_item->getParent()->getName();
-    $entity = $field_item->getEntity();
-    $field_uri = $this->linkManager->getRelationUri($entity->getEntityTypeId(), $entity->bundle(), $field_name, $context);
+    $field_uri = $this->getFieldRelationUri($field_item);
     return array(
       '_links' => array(
         $field_uri => array($link),
@@ -95,12 +103,19 @@ public function normalize($field_item, $format = NULL, array $context = array())
    * {@inheritdoc}
    */
   protected function constructValue($data, $context) {
+    /** @var \Drupal\Core\Field\FieldItemInterface $field_item */
     $field_item = $context['target_instance'];
     $field_definition = $field_item->getFieldDefinition();
     $target_type = $field_definition->getSetting('target_type');
     $id = $this->entityResolver->resolve($this, $data, $target_type);
     if (isset($id)) {
-      return array('target_id' => $id);
+      $constructed = array('target_id' => $id);
+      foreach ($field_item->getProperties() as $property => $value) {
+        if ($property !== 'target_id' && isset($data[$property])) {
+          $constructed[$property] = $data[$property];
+        }
+      }
+      return $constructed;
     }
     return NULL;
   }
@@ -119,4 +134,21 @@ public function getUuid($data) {
     }
   }
 
+  /**
+   * Gets the relation URI of the field containing an item.
+   *
+   * The relation URI is used as a property key when building the HAL structure.
+   *
+   * @param \Drupal\Core\Field\FieldItemInterface $field_item
+   *   The field item that is being normalized.
+   *
+   * @return string
+   *   The relation URI of the field.
+   */
+  protected function getFieldRelationUri(FieldItemInterface $field_item) {
+    $field_name = $field_item->getParent()->getName();
+    $entity = $field_item->getEntity();
+    return $this->linkManager->getRelationUri($entity->getEntityTypeId(), $entity->bundle(), $field_name);
+  }
+
 }
diff --git a/core/modules/hal/src/Normalizer/FileEntityNormalizer.php b/core/modules/hal/src/Normalizer/FileEntityNormalizer.php
index 46c6094..20cbf1d 100644
--- a/core/modules/hal/src/Normalizer/FileEntityNormalizer.php
+++ b/core/modules/hal/src/Normalizer/FileEntityNormalizer.php
@@ -5,7 +5,8 @@
 use Drupal\Core\Entity\EntityManagerInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\rest\LinkManager\LinkManagerInterface;
-use GuzzleHttp\ClientInterface;
+use Drupal\Core\File\FileSystemInterface;
+use Symfony\Component\Serializer\Exception\RuntimeException;
 
 /**
  * Converts the Drupal entity object structure to a HAL array structure.
@@ -13,6 +14,11 @@
 class FileEntityNormalizer extends ContentEntityNormalizer {
 
   /**
+   * Key used to store actual file data.
+   */
+  const FILE_DATA_KEY = 'data';
+
+  /**
    * The interface or class that this Normalizer supports.
    *
    * @var string
@@ -20,51 +26,55 @@ class FileEntityNormalizer extends ContentEntityNormalizer {
   protected $supportedInterfaceOrClass = 'Drupal\file\FileInterface';
 
   /**
-   * The HTTP client.
-   *
-   * @var \GuzzleHttp\ClientInterface
+   * @var \Drupal\Core\File\FileSystemInterface
    */
-  protected $httpClient;
+  protected $fileSystem;
 
   /**
    * Constructs a FileEntityNormalizer object.
    *
    * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
    *   The entity manager.
-   * @param \GuzzleHttp\ClientInterface $http_client
-   *   The HTTP Client.
    * @param \Drupal\rest\LinkManager\LinkManagerInterface $link_manager
    *   The hypermedia link manager.
    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
    *   The module handler.
+   * @param \Drupal\Core\File\FileSystemInterface $file_system
+   *   The file system.
    */
-  public function __construct(EntityManagerInterface $entity_manager, ClientInterface $http_client, LinkManagerInterface $link_manager, ModuleHandlerInterface $module_handler) {
+  public function __construct(EntityManagerInterface $entity_manager, LinkManagerInterface $link_manager, ModuleHandlerInterface $module_handler, FileSystemInterface $file_system) {
     parent::__construct($link_manager, $entity_manager, $module_handler);
-
-    $this->httpClient = $http_client;
+    $this->fileSystem = $file_system;
   }
 
   /**
    * {@inheritdoc}
    */
-  public function normalize($entity, $format = NULL, array $context = array()) {
-    $data = parent::normalize($entity, $format, $context);
-    // Replace the file url with a full url for the file.
-    $data['uri'][0]['value'] = $this->getEntityUri($entity);
+  public function denormalize($data, $class, $format = NULL, array $context = array()) {
+    // File content can be passed base64 encoded in a special "data" property
+    // under the field data.
+    // That property is not a field, so we remove it before denormalizing the
+    // rest of the file entity.
+    $file_data = $data[self::FILE_DATA_KEY][0]['value'];
+    unset($data[self::FILE_DATA_KEY]);
 
-    return $data;
-  }
+    $entity = parent::denormalize($data, $class, $format, $context);
 
-  /**
-   * {@inheritdoc}
-   */
-  public function denormalize($data, $class, $format = NULL, array $context = array()) {
-    $file_data = (string) $this->httpClient->get($data['uri'][0]['value'])->getBody();
+    // Decode and save to file if it's a new file.
+    if (!isset($context['request_method']) || $context['request_method'] != 'patch') {
+      $file_contents = base64_decode($file_data);
+      $dirname = $this->fileSystem->dirname($entity->getFileUri());
 
-    $path = 'temporary://' . drupal_basename($data['uri'][0]['value']);
-    $data['uri'] = file_unmanaged_save_data($file_data, $path);
+      file_prepare_directory($dirname, FILE_CREATE_DIRECTORY);
+      if ($uri = file_unmanaged_save_data($file_contents, file_build_uri($this->fileSystem->basename($entity->getFilename())))) {
+        $entity->setFileUri($uri);
+      }
+      else {
+        throw new RuntimeException('Failed to write ' . $entity->getFilename());
+      }
+    }
 
-    return $this->entityManager->getStorage('file')->create($data);
+    return $entity;
   }
 
 }
diff --git a/core/modules/hal/src/Tests/FileDenormalizeTest.php b/core/modules/hal/src/Tests/FileDenormalizeTest.php
index f160634..f766d7d 100644
--- a/core/modules/hal/src/Tests/FileDenormalizeTest.php
+++ b/core/modules/hal/src/Tests/FileDenormalizeTest.php
@@ -3,6 +3,7 @@
 namespace Drupal\hal\Tests;
 
 use Drupal\file\Entity\File;
+use Drupal\hal\Normalizer\FileEntityNormalizer;
 use Drupal\simpletest\WebTestBase;
 
 /**
@@ -34,14 +35,17 @@ public function testFileDenormalize() {
     $file = File::create($file_params);
     file_put_contents($file->getFileUri(), 'hello world');
     $file->save();
+    $data = base64_encode(file_get_contents($file_params['uri']));
 
     $serializer = \Drupal::service('serializer');
     $normalized_data = $serializer->normalize($file, 'hal_json');
-    $denormalized = $serializer->denormalize($normalized_data, 'Drupal\file\Entity\File', 'hal_json');
-
+    // Adding data to the entity.
+    $normalized_data[FileEntityNormalizer::FILE_DATA_KEY][0]['value'] = $data;
+    // Use 'patch' to avoid trying to recreate the file.
+    $denormalized = $serializer->denormalize($normalized_data, File::class, 'hal_json', array('request_method' => 'patch'));
     $this->assertTrue($denormalized instanceof File, 'A File instance was created.');
 
-    $this->assertIdentical('temporary://' . $file->getFilename(), $denormalized->getFileUri(), 'The expected file URI was found.');
+    $this->assertIdentical('public://' . $file->getFilename(), $denormalized->getFileUri());
     $this->assertTrue(file_exists($denormalized->getFileUri()), 'The temporary file was found.');
 
     $this->assertIdentical($file->uuid(), $denormalized->uuid(), 'The expected UUID was found');
@@ -49,27 +53,6 @@ public function testFileDenormalize() {
     $this->assertIdentical($file->getFilename(), $denormalized->getFilename(), 'The expected filename was found.');
     $this->assertTrue($denormalized->isPermanent(), 'The file has a permanent status.');
 
-    // Try to denormalize with the file uri only.
-    $file_name = 'test_2.txt';
-    $file_path = 'public://' . $file_name;
-
-    file_put_contents($file_path, 'hello world');
-    $file_uri = file_create_url($file_path);
-
-    $data = array(
-      'uri' => array(
-        array('value' => $file_uri),
-      ),
-    );
-
-    $denormalized = $serializer->denormalize($data, 'Drupal\file\Entity\File', 'hal_json');
-
-    $this->assertIdentical('temporary://' . $file_name, $denormalized->getFileUri(), 'The expected file URI was found.');
-    $this->assertTrue(file_exists($denormalized->getFileUri()), 'The temporary file was found.');
-
-    $this->assertIdentical('text/plain', $denormalized->getMimeType(), 'The expected MIME type was found.');
-    $this->assertIdentical($file_name, $denormalized->getFilename(), 'The expected filename was found.');
-    $this->assertFalse($denormalized->isPermanent(), 'The file has a permanent status.');
   }
 
 }
diff --git a/core/modules/hal/tests/src/Kernel/EntityNormalizeTest.php b/core/modules/hal/tests/src/Kernel/EntityNormalizeTest.php
index 88db403..1d17a7c 100644
--- a/core/modules/hal/tests/src/Kernel/EntityNormalizeTest.php
+++ b/core/modules/hal/tests/src/Kernel/EntityNormalizeTest.php
@@ -4,14 +4,16 @@
 
 use Drupal\comment\Tests\CommentTestTrait;
 use Drupal\comment\Entity\Comment;
+use Drupal\hal\Normalizer\FileEntityNormalizer;
 use Drupal\node\Entity\Node;
 use Drupal\user\Entity\User;
 use Drupal\node\Entity\NodeType;
 use Drupal\taxonomy\Entity\Term;
 use Drupal\taxonomy\Entity\Vocabulary;
+use Drupal\file\Entity\File;
 
 /**
- * Tests that nodes and terms are correctly normalized and denormalized.
+ * Tests that nodes, files and terms are correctly normalized and denormalized.
  *
  * @group hal
  */
@@ -24,7 +26,7 @@ class EntityNormalizeTest extends NormalizerTestBase {
    *
    * @var array
    */
-  public static $modules = array('node', 'taxonomy', 'comment');
+  public static $modules = array('node', 'taxonomy', 'comment', 'file');
 
   /**
    * {@inheritdoc}
@@ -37,6 +39,7 @@ protected function setUp() {
     $this->installSchema('comment', array('comment_entity_statistics'));
     $this->installEntitySchema('taxonomy_term');
     $this->installConfig(['node', 'comment']);
+    $this->installEntitySchema('file');
   }
 
   /**
@@ -203,4 +206,40 @@ public function testComment() {
     $this->assertEqual($original_values, $denormalized_comment_values, 'The expected comment values are restored after normalizing and denormalizing.');
   }
 
+  /**
+   * Tests the normalization of files.
+   */
+  public function testFile() {
+    $user = User::create([
+      'name' => 'fileTestingUser',
+    ]);
+    $user->save();
+
+    $file_uri = 'public://normalization_test_file';
+    $file_contents = 'hello world';
+    $data = base64_encode($file_contents);
+    file_put_contents($file_uri, $file_contents);
+
+    $file = File::create([
+      'uid' => $user->id(),
+      'uri' => $file_uri,
+      'status' => FILE_STATUS_PERMANENT,
+    ]);
+
+    $file->save();
+
+    $original_values = $file->toArray();
+    $normalized = $this->serializer->normalize($file, $this->format);
+
+    // Adding data to the entity.
+    $normalized[FileEntityNormalizer::FILE_DATA_KEY][0]['value'] = $data;
+
+    // Use PATCH to avoid trying to create new file on denormalize.
+    $denormalized_file = $this->serializer->denormalize($normalized, File::class, $this->format, ['request_method' => 'patch']);
+    // Loop over the remaining fields and verify that they are identical.
+    foreach ($original_values as $field_name => $field_values) {
+      $this->assertEquals($field_values, $denormalized_file->get($field_name)->getValue());
+    }
+  }
+
 }
diff --git a/core/modules/hal/tests/src/Kernel/FileFieldNormalizeTest.php b/core/modules/hal/tests/src/Kernel/FileFieldNormalizeTest.php
new file mode 100644
index 0000000..4f4b317
--- /dev/null
+++ b/core/modules/hal/tests/src/Kernel/FileFieldNormalizeTest.php
@@ -0,0 +1,106 @@
+<?php
+
+namespace Drupal\Tests\hal\Kernel;
+
+use Drupal\entity_test\Entity\EntityTest;
+use Drupal\field\Entity\FieldConfig;
+use Drupal\field\Entity\FieldStorageConfig;
+use Drupal\file\Entity\File;
+
+/**
+ * Tests the File entity normalizer.
+ *
+ * @group hal
+ */
+class FileFieldNormalizeTest extends NormalizerTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['entity_test', 'field', 'image', 'hal', 'system', 'file'];
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+    $this->installEntitySchema('file');
+    $this->installSchema('file', ['file_usage']);
+  }
+
+  /**
+   * Tests that file field is identical before and after (de)serialization.
+   */
+  public function testFileFieldNormalize() {
+    $file_name = 'test_file_field_normalize.txt';
+    file_put_contents("public://$file_name", 'hello world');
+    $file = File::create([
+      'uri' => "public://$file_name",
+    ]);
+    $file->save();
+
+    // Attach a file field to the bundle.
+    FieldStorageConfig::create([
+      'type' => 'file',
+      'entity_type' => 'entity_test',
+      'field_name' => 'field_file',
+    ])->save();
+    FieldConfig::create([
+      'field_name' => 'field_file',
+      'entity_type' => 'entity_test',
+      'bundle' => 'entity_test',
+    ])->save();
+
+    // Create an entity referencing the file.
+    $entity = EntityTest::create([
+      'field_file' => [
+        'target_id' => $file->id(),
+        'display' => 0,
+        'description' => 'An attached file',
+      ],
+    ]);
+
+    $serialized = $this->container->get('serializer')->serialize($entity, $this->format);
+    $deserialized = $this->container->get('serializer')->deserialize($serialized, EntityTest::class, $this->format);
+    $this->assertEqual($entity->toArray()['field_file'], $deserialized->toArray()['field_file'], 'File field is preserved.');
+  }
+
+  /**
+   * Tests that image field is identical before and after de/serialization.
+   */
+  public function testImageFieldNormalize() {
+    $file_name = $this->randomMachineName() . '.png';
+    file_put_contents("public://$file_name", $this->randomString());
+    $file = File::create([
+      'uri' => "public://$file_name",
+    ]);
+    $file->save();
+
+    // Attach an image field to the bundle.
+    FieldStorageConfig::create([
+      'type' => 'image',
+      'entity_type' => 'entity_test',
+      'field_name' => 'field_image',
+    ])->save();
+    FieldConfig::create([
+      'field_name' => 'field_image',
+      'entity_type' => 'entity_test',
+      'bundle' => 'entity_test',
+    ])->save();
+
+    // Create an entity referencing the file.
+    $entity = EntityTest::create([
+      'field_image' => [
+        'target_id' => $file->id(),
+        'title' => $this->randomString(),
+        'alt' => $this->randomString(),
+        'width' => 400,
+        'height' => 300,
+      ],
+    ]);
+
+    $serialized = $this->container->get('serializer')->serialize($entity, $this->format);
+    $deserialized = $this->container->get('serializer')->deserialize($serialized, EntityTest::class, $this->format);
+    $this->assertEqual($entity->toArray()['field_image'], $deserialized->toArray()['field_image'], 'Image field is preserved.');
+  }
+}
diff --git a/core/modules/hal/tests/src/Kernel/FileNormalizeTest.php b/core/modules/hal/tests/src/Kernel/FileNormalizeTest.php
index 2aeba89..fdcdb43 100644
--- a/core/modules/hal/tests/src/Kernel/FileNormalizeTest.php
+++ b/core/modules/hal/tests/src/Kernel/FileNormalizeTest.php
@@ -11,6 +11,7 @@
 use Drupal\rest\LinkManager\RelationLinkManager;
 use Drupal\rest\LinkManager\TypeLinkManager;
 use Symfony\Component\Serializer\Serializer;
+use Drupal\hal\Normalizer\FieldNormalizer;
 
 
 /**
@@ -39,8 +40,9 @@ protected function setUp() {
 
     // Set up the mock serializer.
     $normalizers = array(
+      new FieldNormalizer(),
       new FieldItemNormalizer(),
-      new FileEntityNormalizer($entity_manager, \Drupal::httpClient(), $link_manager, \Drupal::moduleHandler()),
+      new FileEntityNormalizer($entity_manager, $link_manager, \Drupal::moduleHandler(), $this->container->get('file_system')),
     );
 
     $encoders = array(
@@ -68,12 +70,17 @@ public function testNormalize() {
     $expected_array = array(
       'uri' => array(
         array(
-          'value' => file_create_url($file->getFileUri())),
+          'value' => $file->getFileUri()),
+      ),
+      'data' => array(
+        array(
+          'value' => base64_encode('hello world'),
+        ),
       ),
     );
 
     $normalized = $this->serializer->normalize($file, $this->format);
-    $this->assertEqual($normalized['uri'], $expected_array['uri'], 'URI is normalized.');
+    $this->assertEqual($normalized['uri'], $expected_array['uri']);
 
   }
 
diff --git a/core/modules/hal/tests/src/Kernel/NormalizerTestBase.php b/core/modules/hal/tests/src/Kernel/NormalizerTestBase.php
index d2899d0..6ae82c7 100644
--- a/core/modules/hal/tests/src/Kernel/NormalizerTestBase.php
+++ b/core/modules/hal/tests/src/Kernel/NormalizerTestBase.php
@@ -9,6 +9,7 @@
 use Drupal\hal\Normalizer\EntityReferenceItemNormalizer;
 use Drupal\hal\Normalizer\FieldItemNormalizer;
 use Drupal\hal\Normalizer\FieldNormalizer;
+use Drupal\hal\Normalizer\FileEntityNormalizer;
 use Drupal\language\Entity\ConfigurableLanguage;
 use Drupal\rest\LinkManager\LinkManager;
 use Drupal\rest\LinkManager\RelationLinkManager;
@@ -130,15 +131,14 @@ protected function setUp() {
       'translatable' => TRUE,
     ])->save();
 
-    $entity_manager = \Drupal::entityManager();
-    $link_manager = new LinkManager(new TypeLinkManager(new MemoryBackend('default'), \Drupal::moduleHandler(), \Drupal::service('config.factory'), \Drupal::service('request_stack')), new RelationLinkManager(new MemoryBackend('default'), $entity_manager, \Drupal::moduleHandler(), \Drupal::service('config.factory'), \Drupal::service('request_stack')));
+    $link_manager = new LinkManager(new TypeLinkManager(new MemoryBackend('default'), $this->container->get('module_handler'), \Drupal::service('config.factory'), \Drupal::service('request_stack')), new RelationLinkManager(new MemoryBackend('default'), $this->container->get('entity.manager'), \Drupal::moduleHandler(), \Drupal::service('config.factory'), \Drupal::service('request_stack')));
 
-    $chain_resolver = new ChainEntityResolver(array(new UuidResolver($entity_manager), new TargetIdResolver()));
+    $chain_resolver = new ChainEntityResolver(array(new UuidResolver($this->container->get('entity.manager')), new TargetIdResolver()));
 
     // Set up the mock serializer.
     $normalizers = array(
-      new ContentEntityNormalizer($link_manager, $entity_manager, \Drupal::moduleHandler()),
-      new EntityReferenceItemNormalizer($link_manager, $chain_resolver),
+      new FileEntityNormalizer($this->container->get('entity.manager'), $link_manager, $this->container->get('module_handler'), $this->container->get('file_system')),
+      new ContentEntityNormalizer($link_manager, $this->container->get('entity.manager'), $this->container->get('module_handler')), new EntityReferenceItemNormalizer($link_manager, $chain_resolver),
       new FieldItemNormalizer(),
       new FieldNormalizer(),
     );
diff --git a/core/modules/rest/src/Plugin/rest/resource/EntityResource.php b/core/modules/rest/src/Plugin/rest/resource/EntityResource.php
index 6ec5f26..2d26208 100644
--- a/core/modules/rest/src/Plugin/rest/resource/EntityResource.php
+++ b/core/modules/rest/src/Plugin/rest/resource/EntityResource.php
@@ -108,9 +108,14 @@ public function post(EntityInterface $entity = NULL) {
       $this->logger->notice('Created entity %type with ID %id.', array('%type' => $entity->getEntityTypeId(), '%id' => $entity->id()));
 
       // 201 Created responses return the newly created entity in the response
-      // body.
-      $url = $entity->urlInfo('canonical', ['absolute' => TRUE])->toString(TRUE);
-      $response = new ResourceResponse($entity, 201, ['Location' => $url->getGeneratedUrl()]);
+      // body. Verify if canonical path exists.
+      if ($entity->hasLinkTemplate('canonical')) {
+        $url = $entity->urlInfo('canonical', ['absolute' => TRUE])->toString(TRUE);
+        $response = new ResourceResponse($entity, 201, ['Location' => $url->getGeneratedUrl()]);
+      }
+      else {
+        $response = new ResourceResponse($entity, 201);
+      }
       // Responses after creating an entity are not cacheable, so we add no
       // cacheability metadata here.
       return $response;
diff --git a/core/modules/rest/src/Tests/FileTest.php b/core/modules/rest/src/Tests/FileTest.php
new file mode 100644
index 0000000..6c3f1f0
--- /dev/null
+++ b/core/modules/rest/src/Tests/FileTest.php
@@ -0,0 +1,131 @@
+<?php
+
+namespace Drupal\rest\Tests;
+
+use Drupal\Core\Url;
+use Drupal\file\Entity\File;
+use Drupal\file\FileInterface;
+use Drupal\hal\Normalizer\FileEntityNormalizer;
+
+/**
+ * Tests CRUD operations for file entity.
+ *
+ * @group rest
+ */
+class FileTest extends RESTTestBase {
+
+  /**
+   * Modules to install.
+   *
+   * @var array
+   */
+  public static $modules = ['hal', 'rest', 'file'];
+
+  /**
+   * The 'serializer' service.
+   *
+   * @var \Symfony\Component\Serializer\Serializer
+   */
+  protected $serializer;
+
+  protected function setUp() {
+    parent::setUp();
+    // Get the 'serializer' service.
+    $this->serializer = $this->container->get('serializer');
+  }
+
+  /**
+   * Tests CRUD operations for the file entity.
+   */
+  public function testCrudFile() {
+    $entity_type = 'file';
+
+    // Enables the REST service for 'file' entity type.
+    $this->enableService('entity:' . $entity_type, 'POST', 'hal_json');
+    $this->enableService('entity:' . $entity_type, 'GET', 'hal_json');
+    $this->enableService('entity:' . $entity_type, 'PATCH');
+    $this->enableService('entity:' . $entity_type, 'DELETE');
+
+    // Allow methods for the current entity type.
+    $permissions[] = 'restful post entity:' . $entity_type;
+    $permissions[] = 'restful get entity:' . $entity_type;
+    $permissions[] = 'restful patch entity:' . $entity_type;
+    $permissions[] = 'restful delete entity:' . $entity_type;
+
+    // Create the user.
+    $account1 = $this->drupalCreateUser($permissions);
+    $account2 = $this->drupalCreateUser($permissions);
+
+    $file_contents = 'hello world';
+    $data = base64_encode($file_contents);
+
+    $file = File::create([
+      'filename' => 'default.txt',
+      'filemime' => 'text/plain',
+      'uid' => $account1->id(),
+    ]);
+
+    $normalized_data = $this->serializer->normalize($file, 'hal_json');
+    $normalized_data[FileEntityNormalizer::FILE_DATA_KEY][0]['value'] = $data;
+
+    // Remove non-modifiable fields.
+    unset($normalized_data['status']);
+    unset($normalized_data['changed']);
+
+    $serialized = $this->serializer->serialize($normalized_data, 'hal_json');
+
+    // Create the file.
+    $this->drupalLogin($account1);
+    $this->httpRequest('entity/' . $entity_type, 'POST', $serialized, 'application/hal+json');
+    $this->assertResponse(201);
+
+    // Get the file.
+    $this->httpRequest(Url::fromUri('base://entity/file/1', ['query' => ['_format' => 'hal_json']]), 'GET', NULL, 'application/hal+json');
+    $this->assertResponse(200);
+
+    // Update the file.
+    $normalized_data['filename'][0]['value'] = 'default2.txt';
+    $serialized = $this->serializer->serialize($normalized_data, 'hal_json');
+    $this->httpRequest('entity/' . $entity_type . '/1', 'PATCH', $serialized, 'application/hal+json');
+    $this->assertResponse(204);
+
+    // Verify that the new name is correct.
+    $file = File::load(1);
+    $this->assertEqual('default2.txt', $file->getFilename(), 'The name was updated as expected');
+
+    $file_uri = $file->getFileUri();
+    $this->assertTrue($this->isFileInFileSystem($file_uri), 'File exists in file system');
+
+    // Only the owner can remove the file.
+    $this->drupalLogin($account2);
+    $this->httpRequest('entity/' . $entity_type . '/1', 'DELETE', NULL, 'application/hal+json');
+    $this->assertResponse(403);
+
+    $this->drupalLogin($account1);
+    $this->httpRequest('entity/' . $entity_type . '/1', 'DELETE', NULL, 'application/hal+json');
+    $this->assertResponse(204);
+
+    \Drupal::entityTypeManager()->getStorage('file')->resetCache([1]);
+    $this->assertFalse($this->isFileInFileSystem($file_uri), 'File no longer exists in file system');
+    $this->assertNull(File::load(1), 'The file no longer exists after delete.');
+
+    // Attempt to get the file again after delete.
+    $this->httpRequest(Url::fromUri('base://entity/file/1', ['query' => ['_format' => 'hal_json']]), 'GET', NULL, 'application/hal+json');
+    $this->assertResponse(404);
+  }
+
+  /**
+   * Determines whether a file is actually in the file system.
+   *
+   * @param $uri
+   *   The URI of the file to check.
+   *
+   * @return bool
+   */
+  protected function isFileInFileSystem($uri) {
+    $path = \Drupal::service('file_system')->realpath($uri);
+    return file_exists($path);
+  }
+
+}
+
diff --git a/core/modules/rest/src/Tests/RESTTestBase.php b/core/modules/rest/src/Tests/RESTTestBase.php
index 71b1a08..6a86681 100644
--- a/core/modules/rest/src/Tests/RESTTestBase.php
+++ b/core/modules/rest/src/Tests/RESTTestBase.php
@@ -258,7 +258,7 @@ protected function entityValues($entity_type) {
   protected function enableService($resource_type, $method = 'GET', $format = NULL, $auth = NULL) {
     // Enable REST API for this entity type.
     $config = $this->config('rest.settings');
-    $settings = array();
+    $settings = $resource_type ? $config->get('resources') : [];
 
     if ($resource_type) {
       if ($format == NULL) {
