net: allow controlling redirects
authordeepak1556 <hop2deep@gmail.com>
Thu, 23 Mar 2017 19:37:54 +0000 (01:07 +0530)
committerdeepak1556 <hop2deep@gmail.com>
Tue, 28 Mar 2017 13:16:21 +0000 (18:46 +0530)
atom/browser/api/atom_api_url_request.cc
atom/browser/api/atom_api_url_request.h
atom/browser/net/atom_url_request.cc
atom/browser/net/atom_url_request.h
lib/browser/api/net.js
spec/api-net-spec.js

index 967ae50..d3607e7 100644 (file)
@@ -8,6 +8,7 @@
 #include "atom/browser/net/atom_url_request.h"
 #include "atom/common/api/event_emitter_caller.h"
 #include "atom/common/native_mate_converters/callback.h"
+#include "atom/common/native_mate_converters/gurl_converter.h"
 #include "atom/common/native_mate_converters/net_converter.h"
 #include "atom/common/native_mate_converters/string16_converter.h"
 #include "atom/common/node_includes.h"
@@ -145,6 +146,8 @@ mate::WrappableBase* URLRequest::New(mate::Arguments* args) {
   dict.Get("method", &method);
   std::string url;
   dict.Get("url", &url);
+  std::string redirect_policy;
+  dict.Get("redirect", &redirect_policy);
   std::string partition;
   mate::Handle<api::Session> session;
   if (dict.Get("session", &session)) {
@@ -156,8 +159,8 @@ mate::WrappableBase* URLRequest::New(mate::Arguments* args) {
   }
   auto browser_context = session->browser_context();
   auto api_url_request = new URLRequest(args->isolate(), args->GetThis());
-  auto atom_url_request =
-      AtomURLRequest::Create(browser_context, method, url, api_url_request);
+  auto atom_url_request = AtomURLRequest::Create(
+      browser_context, method, url, redirect_policy, api_url_request);
 
   api_url_request->atom_request_ = atom_url_request;
 
@@ -176,6 +179,7 @@ void URLRequest::BuildPrototype(v8::Isolate* isolate,
       .SetMethod("setExtraHeader", &URLRequest::SetExtraHeader)
       .SetMethod("removeExtraHeader", &URLRequest::RemoveExtraHeader)
       .SetMethod("setChunkedUpload", &URLRequest::SetChunkedUpload)
+      .SetMethod("followRedirect", &URLRequest::FollowRedirect)
       .SetMethod("_setLoadFlags", &URLRequest::SetLoadFlags)
       .SetProperty("notStarted", &URLRequest::NotStarted)
       .SetProperty("finished", &URLRequest::Finished)
@@ -246,6 +250,17 @@ void URLRequest::Cancel() {
   Close();
 }
 
+void URLRequest::FollowRedirect() {
+  if (request_state_.Canceled() || request_state_.Closed()) {
+    return;
+  }
+
+  DCHECK(atom_request_);
+  if (atom_request_) {
+    atom_request_->FollowRedirect();
+  }
+}
+
 bool URLRequest::SetExtraHeader(const std::string& name,
                                 const std::string& value) {
   // Request state must be in the initial non started state.
@@ -305,6 +320,24 @@ void URLRequest::SetLoadFlags(int flags) {
   }
 }
 
+void URLRequest::OnReceivedRedirect(
+    int status_code,
+    const std::string& method,
+    const GURL& url,
+    scoped_refptr<net::HttpResponseHeaders> response_headers) {
+  if (request_state_.Canceled() || request_state_.Closed()) {
+    return;
+  }
+
+  DCHECK(atom_request_);
+  if (!atom_request_) {
+    return;
+  }
+
+  EmitRequestEvent(false, "redirect", status_code, method, url,
+                   response_headers.get());
+}
+
 void URLRequest::OnAuthenticationRequired(
     scoped_refptr<const net::AuthChallengeInfo> auth_info) {
   if (request_state_.Canceled() || request_state_.Closed()) {
index c92ac01..372ac98 100644 (file)
@@ -99,6 +99,11 @@ class URLRequest : public mate::EventEmitter<URLRequest> {
                              v8::Local<v8::FunctionTemplate> prototype);
 
   // Methods for reporting events into JavaScript.
+  void OnReceivedRedirect(
+      int status_code,
+      const std::string& method,
+      const GURL& url,
+      scoped_refptr<net::HttpResponseHeaders> response_headers);
   void OnAuthenticationRequired(
       scoped_refptr<const net::AuthChallengeInfo> auth_info);
   void OnResponseStarted(
@@ -170,6 +175,7 @@ class URLRequest : public mate::EventEmitter<URLRequest> {
   bool Failed() const;
   bool Write(scoped_refptr<const net::IOBufferWithSize> buffer, bool is_last);
   void Cancel();
+  void FollowRedirect();
   bool SetExtraHeader(const std::string& name, const std::string& value);
   void RemoveExtraHeader(const std::string& name);
   void SetChunkedUpload(bool is_chunked_upload);
index 2c7bb61..19c5267 100644 (file)
@@ -13,6 +13,7 @@
 #include "net/base/io_buffer.h"
 #include "net/base/load_flags.h"
 #include "net/base/upload_bytes_element_reader.h"
+#include "net/url_request/redirect_info.h"
 
 namespace {
 const int kBufferSize = 4096;
@@ -58,6 +59,7 @@ scoped_refptr<AtomURLRequest> AtomURLRequest::Create(
     AtomBrowserContext* browser_context,
     const std::string& method,
     const std::string& url,
+    const std::string& redirect_policy,
     api::URLRequest* delegate) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
@@ -76,7 +78,7 @@ scoped_refptr<AtomURLRequest> AtomURLRequest::Create(
   if (content::BrowserThread::PostTask(
           content::BrowserThread::IO, FROM_HERE,
           base::Bind(&AtomURLRequest::DoInitialize, atom_url_request,
-                     request_context_getter, method, url))) {
+                     request_context_getter, method, url, redirect_policy))) {
     return atom_url_request;
   }
   return nullptr;
@@ -93,10 +95,12 @@ void AtomURLRequest::Terminate() {
 void AtomURLRequest::DoInitialize(
     scoped_refptr<net::URLRequestContextGetter> request_context_getter,
     const std::string& method,
-    const std::string& url) {
+    const std::string& url,
+    const std::string& redirect_policy) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
   DCHECK(request_context_getter);
 
+  redirect_policy_ = redirect_policy;
   request_context_getter_ = request_context_getter;
   request_context_getter_->AddObserver(this);
   auto context = request_context_getter_->GetURLRequestContext();
@@ -150,6 +154,13 @@ void AtomURLRequest::Cancel() {
                                    base::Bind(&AtomURLRequest::DoCancel, this));
 }
 
+void AtomURLRequest::FollowRedirect() {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  content::BrowserThread::PostTask(
+      content::BrowserThread::IO, FROM_HERE,
+      base::Bind(&AtomURLRequest::DoFollowRedirect, this));
+}
+
 void AtomURLRequest::SetExtraHeader(const std::string& name,
                                     const std::string& value) const {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
@@ -246,6 +257,13 @@ void AtomURLRequest::DoCancel() {
   DoTerminate();
 }
 
+void AtomURLRequest::DoFollowRedirect() {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
+  if (request_ && request_->is_redirecting() && redirect_policy_ == "manual") {
+    request_->FollowDeferredRedirect();
+  }
+}
+
 void AtomURLRequest::DoSetExtraHeader(const std::string& name,
                                       const std::string& value) const {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
@@ -297,6 +315,29 @@ void AtomURLRequest::DoSetLoadFlags(int flags) const {
   request_->SetLoadFlags(request_->load_flags() | flags);
 }
 
+void AtomURLRequest::OnReceivedRedirect(net::URLRequest* request,
+                                        const net::RedirectInfo& info,
+                                        bool* defer_redirect) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
+  if (!request_ || redirect_policy_ == "follow")
+    return;
+
+  if (redirect_policy_ == "error") {
+    request->Cancel();
+    DoCancelWithError(
+        "Request cannot follow redirect with the current redirect mode", true);
+  } else if (redirect_policy_ == "manual") {
+    *defer_redirect = true;
+    scoped_refptr<net::HttpResponseHeaders> response_headers =
+        request->response_headers();
+    content::BrowserThread::PostTask(
+        content::BrowserThread::UI, FROM_HERE,
+        base::Bind(&AtomURLRequest::InformDelegateReceivedRedirect, this,
+                   info.status_code, info.new_method, info.new_url,
+                   response_headers));
+  }
+}
+
 void AtomURLRequest::OnAuthRequired(net::URLRequest* request,
                                     net::AuthChallengeInfo* auth_info) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
@@ -399,6 +440,16 @@ bool AtomURLRequest::CopyAndPostBuffer(int bytes_read) {
                  buffer_copy));
 }
 
+void AtomURLRequest::InformDelegateReceivedRedirect(
+    int status_code,
+    const std::string& method,
+    const GURL& url,
+    scoped_refptr<net::HttpResponseHeaders> response_headers) const {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  if (delegate_)
+    delegate_->OnReceivedRedirect(status_code, method, url, response_headers);
+}
+
 void AtomURLRequest::InformDelegateAuthenticationRequired(
     scoped_refptr<net::AuthChallengeInfo> auth_info) const {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
index db00390..654798d 100644 (file)
@@ -30,12 +30,14 @@ class AtomURLRequest : public base::RefCountedThreadSafe<AtomURLRequest>,
       AtomBrowserContext* browser_context,
       const std::string& method,
       const std::string& url,
+      const std::string& redirect_policy,
       api::URLRequest* delegate);
   void Terminate();
 
   bool Write(scoped_refptr<const net::IOBufferWithSize> buffer, bool is_last);
   void SetChunkedUpload(bool is_chunked_upload);
   void Cancel();
+  void FollowRedirect();
   void SetExtraHeader(const std::string& name, const std::string& value) const;
   void RemoveExtraHeader(const std::string& name) const;
   void PassLoginInformation(const base::string16& username,
@@ -44,6 +46,9 @@ class AtomURLRequest : public base::RefCountedThreadSafe<AtomURLRequest>,
 
  protected:
   // Overrides of net::URLRequest::Delegate
+  void OnReceivedRedirect(net::URLRequest* request,
+                          const net::RedirectInfo& info,
+                          bool* defer_redirect) override;
   void OnAuthRequired(net::URLRequest* request,
                       net::AuthChallengeInfo* auth_info) override;
   void OnResponseStarted(net::URLRequest* request) override;
@@ -60,11 +65,13 @@ class AtomURLRequest : public base::RefCountedThreadSafe<AtomURLRequest>,
 
   void DoInitialize(scoped_refptr<net::URLRequestContextGetter>,
                     const std::string& method,
-                    const std::string& url);
+                    const std::string& url,
+                    const std::string& redirect_policy);
   void DoTerminate();
   void DoWriteBuffer(scoped_refptr<const net::IOBufferWithSize> buffer,
                      bool is_last);
   void DoCancel();
+  void DoFollowRedirect();
   void DoSetExtraHeader(const std::string& name,
                         const std::string& value) const;
   void DoRemoveExtraHeader(const std::string& name) const;
@@ -77,6 +84,11 @@ class AtomURLRequest : public base::RefCountedThreadSafe<AtomURLRequest>,
   void ReadResponse();
   bool CopyAndPostBuffer(int bytes_read);
 
+  void InformDelegateReceivedRedirect(
+      int status_code,
+      const std::string& method,
+      const GURL& url,
+      scoped_refptr<net::HttpResponseHeaders> response_headers) const;
   void InformDelegateAuthenticationRequired(
       scoped_refptr<net::AuthChallengeInfo> auth_info) const;
   void InformDelegateResponseStarted(
@@ -92,6 +104,7 @@ class AtomURLRequest : public base::RefCountedThreadSafe<AtomURLRequest>,
   scoped_refptr<net::URLRequestContextGetter> request_context_getter_;
 
   bool is_chunked_upload_;
+  std::string redirect_policy_;
   std::unique_ptr<net::ChunkedUploadDataStream> chunked_stream_;
   std::unique_ptr<net::ChunkedUploadDataStream::Writer> chunked_stream_writer_;
   std::vector<std::unique_ptr<net::UploadElementReader>>
index 10d9039..049cef9 100644 (file)
@@ -156,9 +156,15 @@ class ClientRequest extends EventEmitter {
       urlStr = url.format(urlObj)
     }
 
+    const redirectPolicy = options.redirect || 'follow'
+    if (!['follow', 'error', 'manual'].includes(redirectPolicy)) {
+      throw new Error('redirect mode should be one of follow, error or manual')
+    }
+
     let urlRequestOptions = {
       method: method,
-      url: urlStr
+      url: urlStr,
+      redirect: redirectPolicy
     }
     if (options.session) {
       if (options.session instanceof Session) {
@@ -339,6 +345,10 @@ class ClientRequest extends EventEmitter {
     return this._write(data, encoding, callback, true)
   }
 
+  followRedirect () {
+    this.urlRequest.followRedirect()
+  }
+
   abort () {
     this.urlRequest.cancel()
   }
index 49fe2ed..80f24a4 100644 (file)
@@ -906,6 +906,199 @@ describe('net module', function () {
       urlRequest.end()
     })
 
+    it('should throw if given an invalid redirect mode', function (done) {
+      const requestUrl = '/requestUrl'
+      try {
+        const urlRequest = net.request({
+          url: `${server.url}${requestUrl}`,
+          redirect: 'custom'
+        })
+        urlRequest
+      } catch (exception) {
+        done()
+      }
+    })
+
+    it('should follow redirect when no redirect mode is provided', function (done) {
+      const requestUrl = '/301'
+      server.on('request', function (request, response) {
+        switch (request.url) {
+          case '/301':
+            response.statusCode = '301'
+            response.setHeader('Location', '/200')
+            response.end()
+            break
+          case '/200':
+            response.statusCode = '200'
+            response.end()
+            break
+          default:
+            assert(false)
+        }
+      })
+      const urlRequest = net.request({
+        url: `${server.url}${requestUrl}`
+      })
+      urlRequest.on('response', function (response) {
+        assert.equal(response.statusCode, 200)
+        done()
+      })
+      urlRequest.end()
+    })
+
+    it('should follow redirect chain when no redirect mode is provided', function (done) {
+      const requestUrl = '/redirectChain'
+      server.on('request', function (request, response) {
+        switch (request.url) {
+          case '/redirectChain':
+            response.statusCode = '301'
+            response.setHeader('Location', '/301')
+            response.end()
+            break
+          case '/301':
+            response.statusCode = '301'
+            response.setHeader('Location', '/200')
+            response.end()
+            break
+          case '/200':
+            response.statusCode = '200'
+            response.end()
+            break
+          default:
+            assert(false)
+        }
+      })
+      const urlRequest = net.request({
+        url: `${server.url}${requestUrl}`
+      })
+      urlRequest.on('response', function (response) {
+        assert.equal(response.statusCode, 200)
+        done()
+      })
+      urlRequest.end()
+    })
+
+    it('should not follow redirect when mode is error', function (done) {
+      const requestUrl = '/301'
+      server.on('request', function (request, response) {
+        switch (request.url) {
+          case '/301':
+            response.statusCode = '301'
+            response.setHeader('Location', '/200')
+            response.end()
+            break
+          case '/200':
+            response.statusCode = '200'
+            response.end()
+            break
+          default:
+            assert(false)
+        }
+      })
+      const urlRequest = net.request({
+        url: `${server.url}${requestUrl}`,
+        redirect: 'error'
+      })
+      urlRequest.on('error', function (error) {
+        assert.equal(error.message, 'Request cannot follow redirect with the current redirect mode')
+      })
+      urlRequest.on('close', function () {
+        done()
+      })
+      urlRequest.end()
+    })
+
+    it('should allow follow redirect when mode is manual', function (done) {
+      const requestUrl = '/redirectChain'
+      let redirectCount = 0
+      server.on('request', function (request, response) {
+        switch (request.url) {
+          case '/redirectChain':
+            response.statusCode = '301'
+            response.setHeader('Location', '/301')
+            response.end()
+            break
+          case '/301':
+            response.statusCode = '301'
+            response.setHeader('Location', '/200')
+            response.end()
+            break
+          case '/200':
+            response.statusCode = '200'
+            response.end()
+            break
+          default:
+            assert(false)
+        }
+      })
+      const urlRequest = net.request({
+        url: `${server.url}${requestUrl}`,
+        redirect: 'manual'
+      })
+      urlRequest.on('response', function (response) {
+        assert.equal(response.statusCode, 200)
+        assert.equal(redirectCount, 2)
+        done()
+      })
+      urlRequest.on('redirect', function (status, method, url) {
+        if (url === `${server.url}/301` || url === `${server.url}/200`) {
+          redirectCount += 1
+          urlRequest.followRedirect()
+        }
+      })
+      urlRequest.end()
+    })
+
+    it('should allow cancelling redirect when mode is manual', function (done) {
+      const requestUrl = '/redirectChain'
+      let redirectCount = 0
+      server.on('request', function (request, response) {
+        switch (request.url) {
+          case '/redirectChain':
+            response.statusCode = '301'
+            response.setHeader('Location', '/redirect/1')
+            response.end()
+            break
+          case '/redirect/1':
+            response.statusCode = '200'
+            response.setHeader('Location', '/redirect/2')
+            response.end()
+            break
+          case '/redirect/2':
+            response.statusCode = '200'
+            response.end()
+            break
+          default:
+            assert(false)
+        }
+      })
+      const urlRequest = net.request({
+        url: `${server.url}${requestUrl}`,
+        redirect: 'manual'
+      })
+      urlRequest.on('response', function (response) {
+        assert.equal(response.statusCode, 200)
+        response.pause()
+        response.on('data', function (chunk) {
+        })
+        response.on('end', function () {
+          urlRequest.abort()
+        })
+        response.resume()
+      })
+      urlRequest.on('close', function () {
+        assert.equal(redirectCount, 1)
+        done()
+      })
+      urlRequest.on('redirect', function (status, method, url) {
+        if (url === `${server.url}/redirect/1`) {
+          redirectCount += 1
+          urlRequest.followRedirect()
+        }
+      })
+      urlRequest.end()
+    })
+
     it('should throw if given an invalid session option', function (done) {
       const requestUrl = '/requestUrl'
       try {