Create a URL utility class out of the existing URL parsing code

This is a small refactoring. We should also consider adopting an
open source URL library (I haven't looked at any, yet).

Change-Id: I36e973aa16511a63fbcbae089b887c5a0aa3bc78
diff --git a/mojo/services/network/BUILD.gn b/mojo/services/network/BUILD.gn
index 92e842a..1c6667e 100644
--- a/mojo/services/network/BUILD.gn
+++ b/mojo/services/network/BUILD.gn
@@ -34,6 +34,8 @@
     "network_service_impl.h",
     "upload_element_reader.cc",
     "upload_element_reader.h",
+    "url.cc",
+    "url.h",
     "url_loader_impl.cc",
     "url_loader_impl.h",
   ]
diff --git a/mojo/services/network/url.cc b/mojo/services/network/url.cc
new file mode 100644
index 0000000..9f55708
--- /dev/null
+++ b/mojo/services/network/url.cc
@@ -0,0 +1,76 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/logging.h"
+
+#include "mojo/services/network/url.h"
+
+#include <algorithm>
+
+namespace mojo {
+
+URL::URL(const std::string& str) : str_(str) {
+  parsed_ = Parse();
+}
+
+URL::~URL() {}
+
+bool URL::Parse() {
+  std::string delim("://");
+  std::string::const_iterator proto_end =
+    std::search(str_.begin(), str_.end(), delim.begin(), delim.end());
+  if (proto_end == str_.end()) {
+    return false;
+  }
+  proto_.assign(str_.begin(), proto_end);
+
+  std::string::const_iterator host_start = proto_end + delim.length();
+  std::string::const_iterator path_start = std::find(host_start, str_.end(),
+                                                     '/');
+  std::string::const_iterator host_end = std::find(host_start, path_start,
+                                                   ':');
+  host_.assign(host_start, host_end);
+
+  if (host_end != path_start)
+    port_.assign(host_end + 1, path_start);
+  else
+    port_ = proto_;
+
+  if (path_start != str_.end())
+    path_.assign(path_start, str_.end());
+  else
+    path_.assign("/");
+
+  if (proto_.length() == 0 || host_.length() == 0 || port_.length() == 0 ||
+      path_.length() == 0)
+    return false;
+
+  return true;
+}
+
+bool URL::IsParsed() {
+  return parsed_;
+}
+
+std::string& URL::Proto() {
+  DCHECK(parsed_);
+  return proto_;
+}
+
+std::string& URL::Host() {
+  DCHECK(parsed_);
+  return host_;
+}
+
+std::string& URL::Port() {
+  DCHECK(parsed_);
+  return port_;
+}
+
+std::string& URL::Path() {
+  DCHECK(parsed_);
+  return path_;
+}
+
+} // namespace mojo
diff --git a/mojo/services/network/url.h b/mojo/services/network/url.h
new file mode 100644
index 0000000..683bc43
--- /dev/null
+++ b/mojo/services/network/url.h
@@ -0,0 +1,38 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_SERVICES_NETWORK_URL_H_
+#define MOJO_SERVICES_NETWORK_URL_H_
+
+#include <string>
+
+namespace mojo {
+
+class URL {
+ public:
+  URL(const std::string& str);
+  ~URL();
+
+  bool IsParsed();
+
+  std::string& Proto();
+  std::string& Host();
+  std::string& Port();
+  std::string& Path();
+
+ private:
+  const std::string str_;
+
+  bool Parse();
+
+  bool parsed_;
+  std::string proto_;
+  std::string host_;
+  std::string port_;
+  std::string path_;
+};
+
+}  // namespace mojo
+
+#endif  // MOJO_SERVICES_NETWORK_URL_H_
diff --git a/mojo/services/network/url_loader_impl.cc b/mojo/services/network/url_loader_impl.cc
index e7808a0..5c0917c 100644
--- a/mojo/services/network/url_loader_impl.cc
+++ b/mojo/services/network/url_loader_impl.cc
@@ -72,41 +72,8 @@
   callback.Run(response.Pass());
 }
 
-bool URLLoaderImpl::ParseURL(const std::string& url, std::string& proto,
-                             std::string& host, std::string& port,
-                             std::string& path) {
-  std::string delim("://");
-  std::string::const_iterator proto_end =
-    std::search(url.begin(), url.end(), delim.begin(), delim.end());
-  if (proto_end == url.end()) {
-    return false;
-  }
-  proto.assign(url.begin(), proto_end);
-
-  std::string::const_iterator host_start = proto_end + delim.length();
-  std::string::const_iterator path_start = std::find(host_start, url.end(), '/');
-  std::string::const_iterator host_end = std::find(host_start, path_start, ':');
-  host.assign(host_start, host_end);
-
-  if (host_end != path_start)
-    port.assign(host_end + 1, path_start);
-  else
-    port = proto;
-
-  if (path_start != url.end())
-    path.assign(path_start, url.end());
-  else
-    path.assign("/");
-
-  if (proto.length() == 0 || host.length() == 0 || port.length() == 0 ||
-      path.length() == 0)
-    return false;
-
-  return true;
-}
-
 void URLLoaderImpl::StartInternal(URLRequestPtr request) {
-  std::string url(request->url);
+  std::string url_str(request->url);
   std::string method(request->method);
   std::map<std::string, std::string> extra_headers;
   std::vector<std::unique_ptr<UploadElementReader>> element_readers;
@@ -126,38 +93,42 @@
   asio::io_service io_service;
   bool redirect = false;
 
+  std::unique_ptr<URL> url(new URL(url_str));
+  if (!url->IsParsed()) {
+    LOG(ERROR) << "url parse error";
+    SendError(net::ERR_INVALID_ARGUMENT);
+    return;
+  }
+
   do {
-    std::string proto, host, port, path;
-
-    if (!ParseURL(url, proto, host, port, path)) {
-      LOG(ERROR) << "url parse error";
-      SendError(net::ERR_INVALID_ARGUMENT);
-      break;
-    }
-
     if (redirect) {
       io_service.reset();
       redirect = false;
     }
 
-    if (proto == "https") {
+    if (url->Proto() == "https") {
 #ifdef NETWORK_SERVICE_USE_HTTPS
       asio::ssl::context ctx(asio::ssl::context::sslv23);
       ctx.set_default_verify_paths();
 
       HTTPClient<asio::ssl::stream<tcp::socket>> c(this, io_service, ctx);
-      MojoResult result = c.CreateRequest(host, path, method,
+      MojoResult result = c.CreateRequest(url->Host(), url->Path(), method,
                                           extra_headers, element_readers);
       if (result != MOJO_RESULT_OK) {
         SendError(net::ERR_INVALID_ARGUMENT);
         break;
       }
-      c.Start(host, port);
+      c.Start(url->Host(), url->Port());
       io_service.run();
 
       if (c.status_code_ == 301 || c.status_code_ == 302) {
         redirect = true;
-        url = c.redirect_location_;
+        url.reset(new URL(c.redirect_location_));
+        if (!url->IsParsed()) {
+          LOG(ERROR) << "url parse error";
+          SendError(net::ERR_INVALID_RESPONSE);
+          break;
+        }
       }
 #else
       LOG(INFO) << "https is not built-in. "
@@ -165,20 +136,25 @@
       SendError(net::ERR_INVALID_ARGUMENT);
       break;
 #endif
-    } else if (proto == "http") {
+    } else if (url->Proto() == "http") {
       HTTPClient<tcp::socket> c(this, io_service);
-      MojoResult result = c.CreateRequest(host, path, method,
+      MojoResult result = c.CreateRequest(url->Host(), url->Path(), method,
                                           extra_headers, element_readers);
       if (result != MOJO_RESULT_OK) {
         SendError(net::ERR_INVALID_ARGUMENT);
         break;
       }
-      c.Start(host, port);
+      c.Start(url->Host(), url->Port());
       io_service.run();
 
       if (c.status_code_ == 301 || c.status_code_ == 302) {
         redirect = true;
-        url = c.redirect_location_;
+        url.reset(new URL(c.redirect_location_));
+        if (!url->IsParsed()) {
+          LOG(ERROR) << "url parse error";
+          SendError(net::ERR_INVALID_RESPONSE);
+          break;
+        }
       }
     } else {
       // unknown protocol
diff --git a/mojo/services/network/url_loader_impl.h b/mojo/services/network/url_loader_impl.h
index 56690c1..222479c 100644
--- a/mojo/services/network/url_loader_impl.h
+++ b/mojo/services/network/url_loader_impl.h
@@ -9,6 +9,8 @@
 #include "mojo/services/network/interfaces/url_loader.mojom.h"
 #include "mojo/public/cpp/bindings/binding.h"
 
+#include "mojo/services/network/url.h"
+
 namespace mojo {
 
 class URLLoaderImpl : public URLLoader {
@@ -32,8 +34,6 @@
   void SendError(int error_code);
   void FollowRedirectInternal();
   void SendResponse(URLResponsePtr response);
-  bool ParseURL(const std::string& url, std::string& scheme, std::string& host,
-                std::string& port, std::string& path);
   void StartInternal(URLRequestPtr request);
 
   Callback<void(URLResponsePtr)> callback_;