Repository: minio/minio-js (js)

Issue:
`fPutObject` does not detect content-type automatically
https://github.com/minio/minio-js/blob/master/examples/fput-object.js#L38-L46

`fPutObject` should detect  content-type automatically, but after https://github.com/minio/minio-js/pull/698 , it doesn't work.


Pull request description:
 Make fPutObject automatically detect content-type
This commit also corrects the fput-object.js example and adds functional
tests to check if this feature is working correctly

Fixes #763

Patch:
diff --git a/examples/fput-object.js b/examples/fput-object.js
index 3468378..a4f5059 100644
--- a/examples/fput-object.js
+++ b/examples/fput-object.js
@@ -26,9 +26,15 @@ var s3Client = new Minio.Client({
   secretKey: 'YOUR-SECRETACCESSKEY'
 })
 
+var metaData = {
+  'Content-Type': 'application/octet-stream',
+  'X-Amz-Meta-Testing': 1234,
+  'example': 5678
+}
+
 // Put a file in bucket my-bucketname.
 var file = 'my-testfile'
-s3Client.fPutObject('my-bucketname', 'my-objectname', file, 'application/octet-stream', function(e) {
+s3Client.fPutObject('my-bucketname', 'my-objectname', file, metaData, function(e) {
   if (e) {
     return console.log(e)
   }
diff --git a/src/main/helpers.js b/src/main/helpers.js
index 8c2ee60..24f0b35 100644
--- a/src/main/helpers.js
+++ b/src/main/helpers.js
@@ -295,6 +295,21 @@ export function readableStream(data) {
   s.push(null)
   return s
 }
+
+// Process metadata to insert appropriate value to `content-type` attribute
+export function insertContentType(metaData, filePath) {
+  // check if content-type attribute present in metaData
+  for (var key in metaData) {
+    if (key.toLowerCase() === 'content-type') {
+      return metaData
+    }
+  }
+  // if `content-type` attribute is not present in metadata,
+  // then infer it from the extension in filePath
+  metaData['content-type'] = probeContentType(filePath)
+  return metaData
+}
+
 // Function prepends metadata with the appropriate prefix if it is not already on
 export function prependXAMZMeta(metaData) {
   var newMetadata = Object.assign({}, metaData)
diff --git a/src/main/minio.js b/src/main/minio.js
index 89feed4..d8eba89 100644
--- a/src/main/minio.js
+++ b/src/main/minio.js
@@ -33,7 +33,7 @@ import { extractMetadata, prependXAMZMeta, isValidPrefix, isValidEndpoint, isVal
   uriEscape, uriResourceEscape, isBoolean, isFunction, isNumber,
   isString, isObject, isArray, isValidDate, pipesetup,
   readableStream, isReadableStream, isVirtualHostStyle,
-  makeDateLong, promisify } from './helpers.js'
+  insertContentType, makeDateLong, promisify } from './helpers.js'
 
 import { signV4, presignSignatureV4, postPresignSignatureV4 } from './signing.js'
 
@@ -877,6 +877,9 @@ export class Client {
       throw new TypeError('metaData should be of type "object"')
     }
 
+    // Inserts correct `content-type` attribute based on metaData and filePath
+    insertContentType(metaData, filePath)
+
     //Updates metaData to have the correct prefix if needed
     metaData = prependXAMZMeta(metaData)
     var size


Test patch:
diff --git a/src/test/functional/functional-tests.js b/src/test/functional/functional-tests.js
index 5d5fba7..99d1c54 100644
--- a/src/test/functional/functional-tests.js
+++ b/src/test/functional/functional-tests.js
@@ -255,6 +255,53 @@ describe('functional tests', function() {
       client.fPutObject(bucketName, _100kbObjectName, tmpFileUpload,done)
     })
 
+    step(`statObject(bucketName, objectName, cb)_bucketName:${bucketName}, objectName:${_100kbObjectName}_`, done => {
+      client.statObject(bucketName, _100kbObjectName, (e, stat) => {
+        if (e) return done(e)
+        // As metadata is not provided and there is no file extension,
+        // we default to 'application/octet-stream' as per `probeContentType` function
+        if (stat.metaData && stat.metaData['content-type'] !== 'application/octet-stream') {
+          return done(new Error('content-type mismatch'))
+        }
+        done()
+      })
+    })
+
+    var tmpFileUploadWithExt = `${tmpDir}/${_100kbObjectName}.txt`
+    step(`fPutObject(bucketName, objectName, filePath, metaData, callback)_bucketName:${bucketName}, objectName:${_100kbObjectName}, filePath: ${tmpFileUploadWithExt}, metaData:${metaData}_`, done => {
+      fs.writeFileSync(tmpFileUploadWithExt, _100kb)
+      client.fPutObject(bucketName, _100kbObjectName, tmpFileUploadWithExt, metaData, done)
+    })
+
+    step(`statObject(bucketName, objectName, cb)_bucketName:${bucketName}, objectName:${_100kbObjectName}_`, done => {
+      client.statObject(bucketName, _100kbObjectName, (e, stat) => {
+        if (e) return done(e)
+        // As metadata is provided, even though we have an extension,
+        // the `content-type` should be equal what was declared on the metadata
+        if (stat.metaData && stat.metaData['content-type'] !== 'text/html') {
+          return done(new Error('content-type mismatch'))
+        } else if (!stat.metaData) {
+          return done(new Error('no metadata present'))
+        }
+        done()
+      })
+    })
+
+    step(`fPutObject(bucketName, objectName, filePath, metaData, callback)_bucketName:${bucketName}, objectName:${_100kbObjectName}, filePath: ${tmpFileUploadWithExt}_`, done => {
+      fs.writeFileSync(tmpFileUploadWithExt, _100kb)
+      client.fPutObject(bucketName, _100kbObjectName, tmpFileUploadWithExt, done)
+    })
+
+    step(`statObject(bucketName, objectName, cb)_bucketName:${bucketName}, objectName:${_100kbObjectName}_`, done => {
+      client.statObject(bucketName, _100kbObjectName, (e, stat) => {
+        if (e) return done(e)
+        // As metadata is not provided but we have a file extension,
+        // we need to infer `content-type` from the file extension
+        if (stat.metaData && stat.metaData['content-type'] !== 'text/plain') return done(new Error('content-type mismatch'))
+        done()
+      })
+    })
+
     step(`putObject(bucketName, objectName, stream, size, metaData, callback)_bucketName:${bucketName}, objectName:${_100kbObjectName}, stream:100kb, size:${_100kb.length}, metaData:${metaData}_`, done => {
       var stream = readableStream(_100kb)
       client.putObject(bucketName, _100kbObjectName, stream, _100kb.length, metaData, done)
@@ -620,14 +667,22 @@ describe('functional tests', function() {
   })
   describe('fGetObject-resume', () => {
     var localFile = `${tmpDir}/${_5mbObjectName}`
+    var etag = ''
     step(`putObject(bucketName, objectName, stream, metaData, cb)_bucketName:${bucketName}, objectName:${_5mbObjectName}, stream:5mb_`, done => {
       var stream = readableStream(_5mb)
-      client.putObject(bucketName, _5mbObjectName, stream, _5mb.length, {}, done)
+      client.putObject(bucketName, _5mbObjectName, stream, _5mb.length, {})
+        .then((resp) => {
+          etag = resp
+          done()
+        })
+        .catch(done)
     })
     step(`fGetObject(bucketName, objectName, filePath, callback)_bucketName:${bucketName}, objectName:${_5mbObjectName}, filePath:${localFile}`, done => {
-      var tmpFile = `${tmpDir}/${_5mbObjectName}.${_5mbmd5}.part.minio`
+      var bufPart = new Buffer(_100kb.length)
+      _5mb.copy(bufPart, 0, 0, _100kb.length)
+      var tmpFile = `${tmpDir}/${_5mbObjectName}.${etag}.part.minio`
       // create a partial file
-      fs.writeFileSync(tmpFile, _100kb)
+      fs.writeFileSync(tmpFile, bufPart)
       client.fGetObject(bucketName, _5mbObjectName, localFile)
         .then(() => {
           var md5sum = crypto.createHash('md5').update(fs.readFileSync(localFile)).digest('hex')