Internal change

PiperOrigin-RevId: 530882356
Change-Id: I75666efa036e514cb18ec597a3c41bd27f738456
diff --git a/GOIMPORT/AUTOPATCHES/tests/basic_test.go.patch b/GOIMPORT/AUTOPATCHES/tests/basic_test.go.patch
new file mode 100644
index 0000000..ee314f6
--- /dev/null
+++ b/GOIMPORT/AUTOPATCHES/tests/basic_test.go.patch
@@ -0,0 +1,11 @@
+# This file reports differences detected by import.go since the last import.
+# This is only for human review and not machine consumption.
+# Please see go/thirdpartygo for more information.
+# DO NOT EDIT. This must only be generated by //third_party/golang/import.go.
+
+@@
+-package tests
++package tests_test
+ 
+ import (
+ 	"context"
diff --git a/GOIMPORT/CONFIGURATION b/GOIMPORT/CONFIGURATION
new file mode 100644
index 0000000..d7c8f0d
--- /dev/null
+++ b/GOIMPORT/CONFIGURATION
@@ -0,0 +1,43 @@
+# This file configures imports managed by //third_party/golang/import.go.
+# See go/thirdpartygo for more information.
+
+# ImportFiles specifies which files to import from the upstream source.
+[ImportFiles]
+  exclude: /[.]        # exclude hidden files and folders (e.g., ".gitignore")
+  exclude: /METADATA$  # see go/metadata
+  exclude: /OWNERS$    # see go/owners
+  exclude: /BUILD$     # see go/build
+  include: .
+
+# ImportRenames specifies whether to rename any source files or directories.
+[ImportRenames]
+  # Rename common names for the LICENSE file; adjust if necessary.
+  sed: s:^/LICEN[CS]E([.](gpl|md|txt))?$:/LICENSE:I  # see go/thirdpartylicenses
+
+  # Mangle special characters to comply with Piper and Blaze limitations.
+  sed: `:loop; s:/(.*)[.-](.*)/:/\1_\2/:; t loop;`  # dots/dashes in directories to underscores
+  sed: "s:[ ]:_:g"                                  # spaces to underscores
+  sed: "s:[()]::g"                                  # remove parentheses
+
+# RewriteFiles specifies which Go source files to rewrite import paths for.
+[RewriteFiles]
+  exclude: /testdata/
+  exclude: /[._][^/]*$
+  include: .
+
+# GoogleFiles specifies files added to the import to support use within google3.
+[GoogleFiles]
+  include: ^/GOIMPORT/        # configuration and metadata for import.go tool
+  include: /METADATA          # see go/metadata
+  include: /OWNERS$           # see go/owners
+  include: /BUILD$            # see go/build
+  include: /g3doc/            # see go/g3doc
+  include: /bluze\.textproto$ # see go/bluze
+  include: /[^/]+\.blueprint$ # see go/blueprint
+  include: /copy\.bara\.sky$  # see go/copybara-tp-golang
+
+  # Treat files with google_ prefix as Google-internal. This may conflict with
+  # actual files upstream with a google_ prefix; delete if necessary.
+  include: /google_[^/]+$
+
+  exclude: .
diff --git a/GOIMPORT/MANIFEST b/GOIMPORT/MANIFEST
new file mode 100644
index 0000000..7e1175a
--- /dev/null
+++ b/GOIMPORT/MANIFEST
@@ -0,0 +1,27 @@
+# This file contains a manifest of all files imported by import.go.
+# This is for both human review and machine consumption.
+# Please see go/thirdpartygo for more information.
+# DO NOT EDIT. This must only be generated by //third_party/golang/import.go.
+
++ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx BUILD
++ f8197fcdb088934eb7064177aa870e36 GOIMPORT/AUTOPATCHES/tests/basic_test.go.patch
++ c7b8b763665961ebdfb805595260035e GOIMPORT/CONFIGURATION
++ a6594993e651222a0776d69e53218d6a GOIMPORT/MANIFEST
+= f4f3830490c360b77a253a284506699c LICENSE
++ 409bb1af117f905fb6f00dcc4feeac45 METADATA
+= 8666fba44b51ae89b85bebf80e31363f README.md
++ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx auth/BUILD
+= 9ebd04522e3df08b3a8a1e62c8db7eae auth/key.go
+= 880a3308fa8fd7dd5cfe34aa35c7906c client.go
+= 7c85b7caff69baefbf4e7e16f0d8b27c configurer.go
+= dd7cc13fc35addb549c4100a622e901f go.mod
+= fae750ae9da460fea138f1ea3eb50ded go.sum
+= b0c6e10a5c9edf076f2416fbbe33f4c2 protocol.go
+= 41bd693574e6d41bbafd2f4b215da976 scp.go
++ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx tests/BUILD
+~ 36aa47365f5c161f3f425c9b31cd599a tests/basic_test.go
+= 4f146b00cd6cd8d20678db1b685e47e4 tests/data/Exöt1ç_download_file.txt.txt
+= b3d0330c7ba306c72e1717ea6222a6a8 tests/data/upload_file.txt
+= 166b7213b0e6ec4dc6cdc6bc2c57fc42 tests/entrypoint_d/setpasswd.sh
+= b0e8784a99dacc01286aeec511fad91c tests/run_all.sh
+= b8cc39b0dfc9400416a80138e5c24674 utils.go
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..14e2f77
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,373 @@
+Mozilla Public License Version 2.0
+==================================
+
+1. Definitions
+--------------
+
+1.1. "Contributor"
+    means each individual or legal entity that creates, contributes to
+    the creation of, or owns Covered Software.
+
+1.2. "Contributor Version"
+    means the combination of the Contributions of others (if any) used
+    by a Contributor and that particular Contributor's Contribution.
+
+1.3. "Contribution"
+    means Covered Software of a particular Contributor.
+
+1.4. "Covered Software"
+    means Source Code Form to which the initial Contributor has attached
+    the notice in Exhibit A, the Executable Form of such Source Code
+    Form, and Modifications of such Source Code Form, in each case
+    including portions thereof.
+
+1.5. "Incompatible With Secondary Licenses"
+    means
+
+    (a) that the initial Contributor has attached the notice described
+        in Exhibit B to the Covered Software; or
+
+    (b) that the Covered Software was made available under the terms of
+        version 1.1 or earlier of the License, but not also under the
+        terms of a Secondary License.
+
+1.6. "Executable Form"
+    means any form of the work other than Source Code Form.
+
+1.7. "Larger Work"
+    means a work that combines Covered Software with other material, in 
+    a separate file or files, that is not Covered Software.
+
+1.8. "License"
+    means this document.
+
+1.9. "Licensable"
+    means having the right to grant, to the maximum extent possible,
+    whether at the time of the initial grant or subsequently, any and
+    all of the rights conveyed by this License.
+
+1.10. "Modifications"
+    means any of the following:
+
+    (a) any file in Source Code Form that results from an addition to,
+        deletion from, or modification of the contents of Covered
+        Software; or
+
+    (b) any new file in Source Code Form that contains any Covered
+        Software.
+
+1.11. "Patent Claims" of a Contributor
+    means any patent claim(s), including without limitation, method,
+    process, and apparatus claims, in any patent Licensable by such
+    Contributor that would be infringed, but for the grant of the
+    License, by the making, using, selling, offering for sale, having
+    made, import, or transfer of either its Contributions or its
+    Contributor Version.
+
+1.12. "Secondary License"
+    means either the GNU General Public License, Version 2.0, the GNU
+    Lesser General Public License, Version 2.1, the GNU Affero General
+    Public License, Version 3.0, or any later versions of those
+    licenses.
+
+1.13. "Source Code Form"
+    means the form of the work preferred for making modifications.
+
+1.14. "You" (or "Your")
+    means an individual or a legal entity exercising rights under this
+    License. For legal entities, "You" includes any entity that
+    controls, is controlled by, or is under common control with You. For
+    purposes of this definition, "control" means (a) the power, direct
+    or indirect, to cause the direction or management of such entity,
+    whether by contract or otherwise, or (b) ownership of more than
+    fifty percent (50%) of the outstanding shares or beneficial
+    ownership of such entity.
+
+2. License Grants and Conditions
+--------------------------------
+
+2.1. Grants
+
+Each Contributor hereby grants You a world-wide, royalty-free,
+non-exclusive license:
+
+(a) under intellectual property rights (other than patent or trademark)
+    Licensable by such Contributor to use, reproduce, make available,
+    modify, display, perform, distribute, and otherwise exploit its
+    Contributions, either on an unmodified basis, with Modifications, or
+    as part of a Larger Work; and
+
+(b) under Patent Claims of such Contributor to make, use, sell, offer
+    for sale, have made, import, and otherwise transfer either its
+    Contributions or its Contributor Version.
+
+2.2. Effective Date
+
+The licenses granted in Section 2.1 with respect to any Contribution
+become effective for each Contribution on the date the Contributor first
+distributes such Contribution.
+
+2.3. Limitations on Grant Scope
+
+The licenses granted in this Section 2 are the only rights granted under
+this License. No additional rights or licenses will be implied from the
+distribution or licensing of Covered Software under this License.
+Notwithstanding Section 2.1(b) above, no patent license is granted by a
+Contributor:
+
+(a) for any code that a Contributor has removed from Covered Software;
+    or
+
+(b) for infringements caused by: (i) Your and any other third party's
+    modifications of Covered Software, or (ii) the combination of its
+    Contributions with other software (except as part of its Contributor
+    Version); or
+
+(c) under Patent Claims infringed by Covered Software in the absence of
+    its Contributions.
+
+This License does not grant any rights in the trademarks, service marks,
+or logos of any Contributor (except as may be necessary to comply with
+the notice requirements in Section 3.4).
+
+2.4. Subsequent Licenses
+
+No Contributor makes additional grants as a result of Your choice to
+distribute the Covered Software under a subsequent version of this
+License (see Section 10.2) or under the terms of a Secondary License (if
+permitted under the terms of Section 3.3).
+
+2.5. Representation
+
+Each Contributor represents that the Contributor believes its
+Contributions are its original creation(s) or it has sufficient rights
+to grant the rights to its Contributions conveyed by this License.
+
+2.6. Fair Use
+
+This License is not intended to limit any rights You have under
+applicable copyright doctrines of fair use, fair dealing, or other
+equivalents.
+
+2.7. Conditions
+
+Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
+in Section 2.1.
+
+3. Responsibilities
+-------------------
+
+3.1. Distribution of Source Form
+
+All distribution of Covered Software in Source Code Form, including any
+Modifications that You create or to which You contribute, must be under
+the terms of this License. You must inform recipients that the Source
+Code Form of the Covered Software is governed by the terms of this
+License, and how they can obtain a copy of this License. You may not
+attempt to alter or restrict the recipients' rights in the Source Code
+Form.
+
+3.2. Distribution of Executable Form
+
+If You distribute Covered Software in Executable Form then:
+
+(a) such Covered Software must also be made available in Source Code
+    Form, as described in Section 3.1, and You must inform recipients of
+    the Executable Form how they can obtain a copy of such Source Code
+    Form by reasonable means in a timely manner, at a charge no more
+    than the cost of distribution to the recipient; and
+
+(b) You may distribute such Executable Form under the terms of this
+    License, or sublicense it under different terms, provided that the
+    license for the Executable Form does not attempt to limit or alter
+    the recipients' rights in the Source Code Form under this License.
+
+3.3. Distribution of a Larger Work
+
+You may create and distribute a Larger Work under terms of Your choice,
+provided that You also comply with the requirements of this License for
+the Covered Software. If the Larger Work is a combination of Covered
+Software with a work governed by one or more Secondary Licenses, and the
+Covered Software is not Incompatible With Secondary Licenses, this
+License permits You to additionally distribute such Covered Software
+under the terms of such Secondary License(s), so that the recipient of
+the Larger Work may, at their option, further distribute the Covered
+Software under the terms of either this License or such Secondary
+License(s).
+
+3.4. Notices
+
+You may not remove or alter the substance of any license notices
+(including copyright notices, patent notices, disclaimers of warranty,
+or limitations of liability) contained within the Source Code Form of
+the Covered Software, except that You may alter any license notices to
+the extent required to remedy known factual inaccuracies.
+
+3.5. Application of Additional Terms
+
+You may choose to offer, and to charge a fee for, warranty, support,
+indemnity or liability obligations to one or more recipients of Covered
+Software. However, You may do so only on Your own behalf, and not on
+behalf of any Contributor. You must make it absolutely clear that any
+such warranty, support, indemnity, or liability obligation is offered by
+You alone, and You hereby agree to indemnify every Contributor for any
+liability incurred by such Contributor as a result of warranty, support,
+indemnity or liability terms You offer. You may include additional
+disclaimers of warranty and limitations of liability specific to any
+jurisdiction.
+
+4. Inability to Comply Due to Statute or Regulation
+---------------------------------------------------
+
+If it is impossible for You to comply with any of the terms of this
+License with respect to some or all of the Covered Software due to
+statute, judicial order, or regulation then You must: (a) comply with
+the terms of this License to the maximum extent possible; and (b)
+describe the limitations and the code they affect. Such description must
+be placed in a text file included with all distributions of the Covered
+Software under this License. Except to the extent prohibited by statute
+or regulation, such description must be sufficiently detailed for a
+recipient of ordinary skill to be able to understand it.
+
+5. Termination
+--------------
+
+5.1. The rights granted under this License will terminate automatically
+if You fail to comply with any of its terms. However, if You become
+compliant, then the rights granted under this License from a particular
+Contributor are reinstated (a) provisionally, unless and until such
+Contributor explicitly and finally terminates Your grants, and (b) on an
+ongoing basis, if such Contributor fails to notify You of the
+non-compliance by some reasonable means prior to 60 days after You have
+come back into compliance. Moreover, Your grants from a particular
+Contributor are reinstated on an ongoing basis if such Contributor
+notifies You of the non-compliance by some reasonable means, this is the
+first time You have received notice of non-compliance with this License
+from such Contributor, and You become compliant prior to 30 days after
+Your receipt of the notice.
+
+5.2. If You initiate litigation against any entity by asserting a patent
+infringement claim (excluding declaratory judgment actions,
+counter-claims, and cross-claims) alleging that a Contributor Version
+directly or indirectly infringes any patent, then the rights granted to
+You by any and all Contributors for the Covered Software under Section
+2.1 of this License shall terminate.
+
+5.3. In the event of termination under Sections 5.1 or 5.2 above, all
+end user license agreements (excluding distributors and resellers) which
+have been validly granted by You or Your distributors under this License
+prior to termination shall survive termination.
+
+************************************************************************
+*                                                                      *
+*  6. Disclaimer of Warranty                                           *
+*  -------------------------                                           *
+*                                                                      *
+*  Covered Software is provided under this License on an "as is"       *
+*  basis, without warranty of any kind, either expressed, implied, or  *
+*  statutory, including, without limitation, warranties that the       *
+*  Covered Software is free of defects, merchantable, fit for a        *
+*  particular purpose or non-infringing. The entire risk as to the     *
+*  quality and performance of the Covered Software is with You.        *
+*  Should any Covered Software prove defective in any respect, You     *
+*  (not any Contributor) assume the cost of any necessary servicing,   *
+*  repair, or correction. This disclaimer of warranty constitutes an   *
+*  essential part of this License. No use of any Covered Software is   *
+*  authorized under this License except under this disclaimer.         *
+*                                                                      *
+************************************************************************
+
+************************************************************************
+*                                                                      *
+*  7. Limitation of Liability                                          *
+*  --------------------------                                          *
+*                                                                      *
+*  Under no circumstances and under no legal theory, whether tort      *
+*  (including negligence), contract, or otherwise, shall any           *
+*  Contributor, or anyone who distributes Covered Software as          *
+*  permitted above, be liable to You for any direct, indirect,         *
+*  special, incidental, or consequential damages of any character      *
+*  including, without limitation, damages for lost profits, loss of    *
+*  goodwill, work stoppage, computer failure or malfunction, or any    *
+*  and all other commercial damages or losses, even if such party      *
+*  shall have been informed of the possibility of such damages. This   *
+*  limitation of liability shall not apply to liability for death or   *
+*  personal injury resulting from such party's negligence to the       *
+*  extent applicable law prohibits such limitation. Some               *
+*  jurisdictions do not allow the exclusion or limitation of           *
+*  incidental or consequential damages, so this exclusion and          *
+*  limitation may not apply to You.                                    *
+*                                                                      *
+************************************************************************
+
+8. Litigation
+-------------
+
+Any litigation relating to this License may be brought only in the
+courts of a jurisdiction where the defendant maintains its principal
+place of business and such litigation shall be governed by laws of that
+jurisdiction, without reference to its conflict-of-law provisions.
+Nothing in this Section shall prevent a party's ability to bring
+cross-claims or counter-claims.
+
+9. Miscellaneous
+----------------
+
+This License represents the complete agreement concerning the subject
+matter hereof. If any provision of this License is held to be
+unenforceable, such provision shall be reformed only to the extent
+necessary to make it enforceable. Any law or regulation which provides
+that the language of a contract shall be construed against the drafter
+shall not be used to construe this License against a Contributor.
+
+10. Versions of the License
+---------------------------
+
+10.1. New Versions
+
+Mozilla Foundation is the license steward. Except as provided in Section
+10.3, no one other than the license steward has the right to modify or
+publish new versions of this License. Each version will be given a
+distinguishing version number.
+
+10.2. Effect of New Versions
+
+You may distribute the Covered Software under the terms of the version
+of the License under which You originally received the Covered Software,
+or under the terms of any subsequent version published by the license
+steward.
+
+10.3. Modified Versions
+
+If you create software not governed by this License, and you want to
+create a new license for such software, you may create and use a
+modified version of this License if you rename the license and remove
+any references to the name of the license steward (except to note that
+such modified license differs from this License).
+
+10.4. Distributing Source Code Form that is Incompatible With Secondary
+Licenses
+
+If You choose to distribute Source Code Form that is Incompatible With
+Secondary Licenses under the terms of this version of the License, the
+notice described in Exhibit B of this License must be attached.
+
+Exhibit A - Source Code Form License Notice
+-------------------------------------------
+
+  This Source Code Form is subject to the terms of the Mozilla Public
+  License, v. 2.0. If a copy of the MPL was not distributed with this
+  file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+If it is not possible or desirable to put the notice in a particular
+file, then You may include the notice in a location (such as a LICENSE
+file in a relevant directory) where a recipient would be likely to look
+for such a notice.
+
+You may add additional accurate notices of copyright ownership.
+
+Exhibit B - "Incompatible With Secondary Licenses" Notice
+---------------------------------------------------------
+
+  This Source Code Form is "Incompatible With Secondary Licenses", as
+  defined by the Mozilla Public License, v. 2.0.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..041988d
--- /dev/null
+++ b/README.md
@@ -0,0 +1,97 @@
+Copy files over SCP with Go
+=============================
+[![Go Report Card](https://goreportcard.com/badge/bramvdbogaerde/go-scp)](https://goreportcard.com/report/bramvdbogaerde/go-scp) [![](https://godoc.org/github.com/bramvdbogaerde/go-scp?status.svg)](https://godoc.org/github.com/bramvdbogaerde/go-scp)
+
+This package makes it very easy to copy files over scp in Go.
+It uses the golang.org/x/crypto/ssh package to establish a secure connection to a remote server in order to copy the files via the SCP protocol.
+
+### Example usage
+
+
+```go
+package main
+
+import (
+	"fmt"
+	scp "github.com/bramvdbogaerde/go-scp"
+	"github.com/bramvdbogaerde/go-scp/auth"
+	"golang.org/x/crypto/ssh"
+	"os"
+        "context"
+)
+
+func main() {
+	// Use SSH key authentication from the auth package
+	// we ignore the host key in this example, please change this if you use this library
+	clientConfig, _ := auth.PrivateKey("username", "/path/to/rsa/key", ssh.InsecureIgnoreHostKey())
+
+	// For other authentication methods see ssh.ClientConfig and ssh.AuthMethod
+
+	// Create a new SCP client
+	client := scp.NewClient("example.com:22", &clientConfig)
+
+	// Connect to the remote server
+	err := client.Connect()
+	if err != nil {
+		fmt.Println("Couldn't establish a connection to the remote server ", err)
+		return
+	}
+
+	// Open a file
+	f, _ := os.Open("/path/to/local/file")
+
+	// Close client connection after the file has been copied
+	defer client.Close()
+
+	// Close the file after it has been copied
+	defer f.Close()
+
+	// Finaly, copy the file over
+	// Usage: CopyFromFile(context, file, remotePath, permission)
+
+        // the context can be adjusted to provide time-outs or inherit from other contexts if this is embedded in a larger application.
+	err = client.CopyFromFile(context.Background(), *f, "/home/server/test.txt", "0655")
+
+	if err != nil {
+		fmt.Println("Error while copying file ", err)
+	}
+}
+```
+
+#### Using an existing SSH connection
+
+If you have an existing established SSH connection, you can use that instead.
+
+```go
+func connectSSH() *ssh.Client {
+   // setup SSH connection
+}
+
+func main() {
+   sshClient := connectSSH()
+
+   // Create a new SCP client, note that this function might
+   // return an error, as a new SSH session is established using the existing connecton
+
+   client, err := scp.NewClientBySSH(sshClient)
+   if err != nil {
+      fmt.Println("Error creating new SSH session from existing connection", err)
+   }
+
+   /* .. same as above .. */
+}
+```
+
+#### Copying Files from Remote Server
+
+It is also possible to copy remote files using this library. 
+The usage is similar to the example at the top of this section, except that `CopyFromRemote` needsto be used instead.
+
+For a more comprehensive example, please consult the `TestDownloadFile` function in t he `tests/basic_test.go` file.
+
+### License
+
+This library is licensed under the Mozilla Public License 2.0.    
+A copy of the license is provided in the `LICENSE.txt` file.
+
+Copyright (c) 2020 Bram Vandenbogaerde
diff --git a/auth/key.go b/auth/key.go
new file mode 100644
index 0000000..6d3cb9b
--- /dev/null
+++ b/auth/key.go
@@ -0,0 +1,90 @@
+/* Copyright (c) 2020 Bram Vandenbogaerde
+ * You may use, distribute or modify this code under the
+ * terms of the Mozilla Public License 2.0, which is distributed
+ * along with the source code.
+ */
+package auth
+
+import (
+	"io/ioutil"
+	"net"
+	"os"
+
+	"google3/third_party/golang/go_crypto/ssh/agent/agent"
+	"google3/third_party/golang/go_crypto/ssh/ssh"
+)
+
+// PrivateKey Loads a private and public key from "path" and returns a SSH ClientConfig to authenticate with the server
+func PrivateKey(username string, path string, keyCallBack ssh.HostKeyCallback) (ssh.ClientConfig, error) {
+	privateKey, err := ioutil.ReadFile(path)
+
+	if err != nil {
+		return ssh.ClientConfig{}, err
+	}
+
+	signer, err := ssh.ParsePrivateKey(privateKey)
+
+	if err != nil {
+		return ssh.ClientConfig{}, err
+	}
+
+	return ssh.ClientConfig{
+		User: username,
+		Auth: []ssh.AuthMethod{
+			ssh.PublicKeys(signer),
+		},
+		HostKeyCallback: keyCallBack,
+	}, nil
+}
+
+// Creates the configuration for a client that authenticates with a password protected private key
+func PrivateKeyWithPassphrase(username string, passpharase []byte, path string, keyCallBack ssh.HostKeyCallback) (ssh.ClientConfig, error) {
+	privateKey, err := ioutil.ReadFile(path)
+
+	if err != nil {
+		return ssh.ClientConfig{}, err
+	}
+	signer, err := ssh.ParsePrivateKeyWithPassphrase(privateKey, passpharase)
+
+	if err != nil {
+		return ssh.ClientConfig{}, err
+	}
+
+	return ssh.ClientConfig{
+		User: username,
+		Auth: []ssh.AuthMethod{
+			ssh.PublicKeys(signer),
+		},
+		HostKeyCallback: keyCallBack,
+	}, nil
+}
+
+// Creates a configuration for a client that fetches public-private key from the SSH agent for authentication
+func SshAgent(username string, keyCallBack ssh.HostKeyCallback) (ssh.ClientConfig, error) {
+	socket := os.Getenv("SSH_AUTH_SOCK")
+	conn, err := net.Dial("unix", socket)
+	if err != nil {
+		return ssh.ClientConfig{}, err
+	}
+
+	agentClient := agent.NewClient(conn)
+	return ssh.ClientConfig{
+		User: username,
+		Auth: []ssh.AuthMethod{
+			ssh.PublicKeysCallback(agentClient.Signers),
+		},
+		HostKeyCallback: keyCallBack,
+	}, nil
+}
+
+// Creates a configuration for a client that authenticates using username and password
+func PasswordKey(username string, password string, keyCallBack ssh.HostKeyCallback) (ssh.ClientConfig, error) {
+
+	return ssh.ClientConfig{
+		User: username,
+		Auth: []ssh.AuthMethod{
+			ssh.Password(password),
+		},
+		HostKeyCallback: keyCallBack,
+	}, nil
+}
diff --git a/client.go b/client.go
new file mode 100644
index 0000000..00f1729
--- /dev/null
+++ b/client.go
@@ -0,0 +1,341 @@
+/* Copyright (c) 2021 Bram Vandenbogaerde And Contributors
+ * You may use, distribute or modify this code under the
+ * terms of the Mozilla Public License 2.0, which is distributed
+ * along with the source code.
+ */
+
+package scp
+
+import (
+	"bytes"
+	"context"
+	"errors"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"os"
+	"path"
+	"sync"
+	"time"
+
+	"google3/third_party/golang/go_crypto/ssh/ssh"
+)
+
+type PassThru func(r io.Reader, total int64) io.Reader
+
+type Client struct {
+	// Host the host to connect to.
+	Host string
+
+	// ClientConfig the client config to use.
+	ClientConfig *ssh.ClientConfig
+
+	// Session stores the SSH session while the connection is running.
+	Session *ssh.Session
+
+	// Conn stores the SSH connection itself in order to close it after transfer.
+	Conn ssh.Conn
+
+	// Timeout the maximal amount of time to wait for a file transfer to complete.
+	// Deprecated: use context.Context for each function instead.
+	Timeout time.Duration
+
+	// RemoteBinary the absolute path to the remote SCP binary.
+	RemoteBinary string
+}
+
+// Connect connects to the remote SSH server, returns error if it couldn't establish a session to the SSH server.
+func (a *Client) Connect() error {
+	if a.Session != nil {
+		return nil
+	}
+
+	client, err := ssh.Dial("tcp", a.Host, a.ClientConfig)
+	if err != nil {
+		return err
+	}
+
+	a.Conn = client.Conn
+	a.Session, err = client.NewSession()
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+// CopyFromFile copies the contents of an os.File to a remote location, it will get the length of the file by looking it up from the filesystem.
+func (a *Client) CopyFromFile(ctx context.Context, file os.File, remotePath string, permissions string) error {
+	return a.CopyFromFilePassThru(ctx, file, remotePath, permissions, nil)
+}
+
+// CopyFromFilePassThru copies the contents of an os.File to a remote location, it will get the length of the file by looking it up from the filesystem.
+// Access copied bytes by providing a PassThru reader factory.
+func (a *Client) CopyFromFilePassThru(ctx context.Context, file os.File, remotePath string, permissions string, passThru PassThru) error {
+	stat, err := file.Stat()
+	if err != nil {
+		return fmt.Errorf("failed to stat file: %w", err)
+	}
+	return a.CopyPassThru(ctx, &file, remotePath, permissions, stat.Size(), passThru)
+}
+
+// CopyFile copies the contents of an io.Reader to a remote location, the length is determined by reading the io.Reader until EOF
+// if the file length in know in advance please use "Copy" instead.
+func (a *Client) CopyFile(ctx context.Context, fileReader io.Reader, remotePath string, permissions string) error {
+	return a.CopyFilePassThru(ctx, fileReader, remotePath, permissions, nil)
+}
+
+// CopyFilePassThru copies the contents of an io.Reader to a remote location, the length is determined by reading the io.Reader until EOF
+// if the file length in know in advance please use "Copy" instead.
+// Access copied bytes by providing a PassThru reader factory.
+func (a *Client) CopyFilePassThru(ctx context.Context, fileReader io.Reader, remotePath string, permissions string, passThru PassThru) error {
+	contentsBytes, err := ioutil.ReadAll(fileReader)
+	if err != nil {
+		return fmt.Errorf("failed to read all data from reader: %w", err)
+	}
+	bytesReader := bytes.NewReader(contentsBytes)
+
+	return a.CopyPassThru(ctx, bytesReader, remotePath, permissions, int64(len(contentsBytes)), passThru)
+}
+
+// wait waits for the waitgroup for the specified max timeout.
+// Returns true if waiting timed out.
+func wait(wg *sync.WaitGroup, ctx context.Context) error {
+	c := make(chan struct{})
+	go func() {
+		defer close(c)
+		wg.Wait()
+	}()
+
+	select {
+	case <-c:
+		return nil
+
+	case <-ctx.Done():
+		return ctx.Err()
+	}
+}
+
+// checkResponse checks the response it reads from the remote, and will return a single error in case
+// of failure.
+func checkResponse(r io.Reader) error {
+	response, err := ParseResponse(r)
+	if err != nil {
+		return err
+	}
+
+	if response.IsFailure() {
+		return errors.New(response.GetMessage())
+	}
+
+	return nil
+
+}
+
+// Copy copies the contents of an io.Reader to a remote location.
+func (a *Client) Copy(ctx context.Context, r io.Reader, remotePath string, permissions string, size int64) error {
+	return a.CopyPassThru(ctx, r, remotePath, permissions, size, nil)
+}
+
+// CopyPassThru copies the contents of an io.Reader to a remote location.
+// Access copied bytes by providing a PassThru reader factory
+func (a *Client) CopyPassThru(ctx context.Context, r io.Reader, remotePath string, permissions string, size int64, passThru PassThru) error {
+	stdout, err := a.Session.StdoutPipe()
+	if err != nil {
+		return err
+	}
+	w, err := a.Session.StdinPipe()
+	if err != nil {
+		return err
+	}
+	defer w.Close()
+
+	if passThru != nil {
+		r = passThru(r, size)
+	}
+
+	filename := path.Base(remotePath)
+
+	wg := sync.WaitGroup{}
+	wg.Add(2)
+
+	errCh := make(chan error, 2)
+
+	go func() {
+		defer wg.Done()
+		defer w.Close()
+
+		_, err = fmt.Fprintln(w, "C"+permissions, size, filename)
+		if err != nil {
+			errCh <- err
+			return
+		}
+
+		if err = checkResponse(stdout); err != nil {
+			errCh <- err
+			return
+		}
+
+		_, err = io.Copy(w, r)
+		if err != nil {
+			errCh <- err
+			return
+		}
+
+		_, err = fmt.Fprint(w, "\x00")
+		if err != nil {
+			errCh <- err
+			return
+		}
+
+		if err = checkResponse(stdout); err != nil {
+			errCh <- err
+			return
+		}
+	}()
+
+	go func() {
+		defer wg.Done()
+		err := a.Session.Run(fmt.Sprintf("%s -qt %q", a.RemoteBinary, remotePath))
+		if err != nil {
+			errCh <- err
+			return
+		}
+	}()
+
+	if a.Timeout > 0 {
+		var cancel context.CancelFunc
+		ctx, cancel = context.WithTimeout(ctx, a.Timeout)
+		defer cancel()
+	}
+
+	if err := wait(&wg, ctx); err != nil {
+		return err
+	}
+
+	close(errCh)
+	for err := range errCh {
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// CopyFromRemote copies a file from the remote to the local file given by the `file`
+// parameter. Use `CopyFromRemotePassThru` if a more generic writer
+// is desired instead of writing directly to a file on the file system.?
+func (a *Client) CopyFromRemote(ctx context.Context, file *os.File, remotePath string) error {
+	return a.CopyFromRemotePassThru(ctx, file, remotePath, nil)
+}
+
+// CopyFromRemotePassThru copies a file from the remote to the given writer. The passThru parameter can be used
+// to keep track of progress and how many bytes that were download from the remote.
+// `passThru` can be set to nil to disable this behaviour.
+func (a *Client) CopyFromRemotePassThru(ctx context.Context, w io.Writer, remotePath string, passThru PassThru) error {
+	wg := sync.WaitGroup{}
+	errCh := make(chan error, 4)
+
+	wg.Add(1)
+	go func() {
+		var err error
+
+		defer func() {
+			// NOTE: this might send an already sent error another time, but since we only receive opne, this is fine. On the "happy-path" of this function, the error will be `nil` therefore completing the "err<-errCh" at the bottom of the function.
+			errCh <- err
+			// We must unblock the go routine first as we block on reading the channel later
+			wg.Done()
+
+		}()
+
+		r, err := a.Session.StdoutPipe()
+		if err != nil {
+			errCh <- err
+			return
+		}
+
+		in, err := a.Session.StdinPipe()
+		if err != nil {
+			errCh <- err
+			return
+		}
+		defer in.Close()
+
+		err = a.Session.Start(fmt.Sprintf("%s -f %q", a.RemoteBinary, remotePath))
+		if err != nil {
+			errCh <- err
+			return
+		}
+
+		err = Ack(in)
+		if err != nil {
+			errCh <- err
+			return
+		}
+
+		res, err := ParseResponse(r)
+		if err != nil {
+			errCh <- err
+			return
+		}
+		if res.IsFailure() {
+			errCh <- errors.New(res.GetMessage())
+			return
+		}
+
+		infos, err := res.ParseFileInfos()
+		if err != nil {
+			errCh <- err
+			return
+		}
+
+		err = Ack(in)
+		if err != nil {
+			errCh <- err
+			return
+		}
+
+		if passThru != nil {
+			r = passThru(r, infos.Size)
+		}
+
+		_, err = CopyN(w, r, infos.Size)
+		if err != nil {
+			errCh <- err
+			return
+		}
+
+		err = Ack(in)
+		if err != nil {
+			errCh <- err
+			return
+		}
+
+		err = a.Session.Wait()
+		if err != nil {
+			errCh <- err
+			return
+		}
+	}()
+
+	if a.Timeout > 0 {
+		var cancel context.CancelFunc
+		ctx, cancel = context.WithTimeout(ctx, a.Timeout)
+		defer cancel()
+	}
+
+	if err := wait(&wg, ctx); err != nil {
+		return err
+	}
+	finalErr := <-errCh
+	close(errCh)
+	return finalErr
+}
+
+func (a *Client) Close() {
+	if a.Session != nil {
+		a.Session.Close()
+	}
+	if a.Conn != nil {
+		a.Conn.Close()
+	}
+}
diff --git a/configurer.go b/configurer.go
new file mode 100644
index 0000000..a9d8447
--- /dev/null
+++ b/configurer.go
@@ -0,0 +1,82 @@
+/* Copyright (c) 2020 Bram Vandenbogaerde
+ * You may use, distribute or modify this code under the
+ * terms of the Mozilla Public License 2.0, which is distributed
+ * along with the source code.
+ */
+
+package scp
+
+import (
+	"time"
+
+	"google3/third_party/golang/go_crypto/ssh/ssh"
+)
+
+// ClientConfigurer a struct containing all the configuration options
+// used by an scp client.
+type ClientConfigurer struct {
+	host         string
+	clientConfig *ssh.ClientConfig
+	session      *ssh.Session
+	timeout      time.Duration
+	remoteBinary string
+}
+
+// NewConfigurer creates a new client configurer.
+// It takes the required parameters: the host and the ssh.ClientConfig and
+// returns a configurer populated with the default values for the optional
+// parameters.
+//
+// These optional parameters can be set by using the methods provided on the
+// ClientConfigurer struct.
+func NewConfigurer(host string, config *ssh.ClientConfig) *ClientConfigurer {
+	return &ClientConfigurer{
+		host:         host,
+		clientConfig: config,
+		timeout:      0, // no timeout by default
+		remoteBinary: "scp",
+	}
+}
+
+// RemoteBinary sets the path of the location of the remote scp binary
+// Defaults to: /usr/bin/scp.
+func (c *ClientConfigurer) RemoteBinary(path string) *ClientConfigurer {
+	c.remoteBinary = path
+	return c
+}
+
+// Host alters the host of the client connects to.
+func (c *ClientConfigurer) Host(host string) *ClientConfigurer {
+	c.host = host
+	return c
+}
+
+// Timeout Changes the connection timeout.
+// Defaults to one minute.
+func (c *ClientConfigurer) Timeout(timeout time.Duration) *ClientConfigurer {
+	c.timeout = timeout
+	return c
+}
+
+// ClientConfig alters the ssh.ClientConfig.
+func (c *ClientConfigurer) ClientConfig(config *ssh.ClientConfig) *ClientConfigurer {
+	c.clientConfig = config
+	return c
+}
+
+// Session alters the ssh.Session.
+func (c *ClientConfigurer) Session(session *ssh.Session) *ClientConfigurer {
+	c.session = session
+	return c
+}
+
+// Create builds a client with the configuration stored within the ClientConfigurer.
+func (c *ClientConfigurer) Create() Client {
+	return Client{
+		Host:         c.host,
+		ClientConfig: c.clientConfig,
+		Timeout:      c.timeout,
+		RemoteBinary: c.remoteBinary,
+		Session:      c.session,
+	}
+}
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..a8c8124
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,8 @@
+module github.com/bramvdbogaerde/go-scp
+
+go 1.13
+
+require (
+	golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a
+	golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea // indirect
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..45c115f
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,10 @@
+golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc=
+golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea h1:+WiDlPBBaO+h9vPNZi8uJ3k4BkKQB7Iow3aqwHVA5hI=
+golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
diff --git a/protocol.go b/protocol.go
new file mode 100644
index 0000000..ca19d71
--- /dev/null
+++ b/protocol.go
@@ -0,0 +1,125 @@
+/* Copyright (c) 2021 Bram Vandenbogaerde And Contributors
+ * You may use, distribute or modify this code under the
+ * terms of the Mozilla Public License 2.0, which is distributed
+ * along with the source code.
+ */
+
+package scp
+
+import (
+	"bufio"
+	"errors"
+	"io"
+	"strconv"
+	"strings"
+)
+
+type ResponseType = uint8
+
+const (
+	Ok      ResponseType = 0
+	Warning ResponseType = 1
+	Error   ResponseType = 2
+)
+
+// Response represent a response from the SCP command.
+// There are tree types of responses that the remote can send back:
+// ok, warning and error
+//
+// The difference between warning and error is that the connection is not closed by the remote,
+// however, a warning can indicate a file transfer failure (such as invalid destination directory)
+// and such be handled as such.
+//
+// All responses except for the `Ok` type always have a message (although these can be empty)
+//
+// The remote sends a confirmation after every SCP command, because a failure can occur after every
+// command, the response should be read and checked after sending them.
+type Response struct {
+	Type    ResponseType
+	Message string
+}
+
+// ParseResponse reads from the given reader (assuming it is the output of the remote) and parses it into a Response structure.
+func ParseResponse(reader io.Reader) (Response, error) {
+	buffer := make([]uint8, 1)
+	_, err := reader.Read(buffer)
+	if err != nil {
+		return Response{}, err
+	}
+
+	responseType := buffer[0]
+	message := ""
+	if responseType > 0 {
+		bufferedReader := bufio.NewReader(reader)
+		message, err = bufferedReader.ReadString('\n')
+		if err != nil {
+			return Response{}, err
+		}
+	}
+
+	return Response{responseType, message}, nil
+}
+
+func (r *Response) IsOk() bool {
+	return r.Type == Ok
+}
+
+func (r *Response) IsWarning() bool {
+	return r.Type == Warning
+}
+
+// IsError returns true when the remote responded with an error.
+func (r *Response) IsError() bool {
+	return r.Type == Error
+}
+
+// IsFailure returns true when the remote answered with a warning or an error.
+func (r *Response) IsFailure() bool {
+	return r.IsWarning() || r.IsError()
+}
+
+// GetMessage returns the message the remote sent back.
+func (r *Response) GetMessage() string {
+	return r.Message
+}
+
+type FileInfos struct {
+	Message     string
+	Filename    string
+	Permissions string
+	Size        int64
+}
+
+func (r *Response) ParseFileInfos() (*FileInfos, error) {
+	message := strings.ReplaceAll(r.Message, "\n", "")
+	parts := strings.Split(message, " ")
+	if len(parts) < 3 {
+		return nil, errors.New("unable to parse message as file infos")
+	}
+
+	size, err := strconv.Atoi(parts[1])
+	if err != nil {
+		return nil, err
+	}
+
+	return &FileInfos{
+		Message:     r.Message,
+		Permissions: parts[0],
+		Size:        int64(size),
+		Filename:    parts[2],
+	}, nil
+}
+
+// Ack writes an `Ack` message to the remote, does not await its response, a seperate call to ParseResponse is
+// therefore required to check if the acknowledgement succeeded.
+func Ack(writer io.Writer) error {
+	var msg = []byte{0}
+	n, err := writer.Write(msg)
+	if err != nil {
+		return err
+	}
+	if n < len(msg) {
+		return errors.New("failed to write ack buffer")
+	}
+	return nil
+}
diff --git a/scp.go b/scp.go
new file mode 100644
index 0000000..d86fb35
--- /dev/null
+++ b/scp.go
@@ -0,0 +1,45 @@
+/* Copyright (c) 2021 Bram Vandenbogaerde
+ * You may use, distribute or modify this code under the
+ * terms of the Mozilla Public License 2.0, which is distributed
+ * along with the source code.
+ */
+
+// Package scp.
+// Simple scp package to copy files over SSH.
+package scp
+
+import (
+	"time"
+
+	"google3/third_party/golang/go_crypto/ssh/ssh"
+)
+
+// NewClient returns a new scp.Client with provided host and ssh.clientConfig.
+func NewClient(host string, config *ssh.ClientConfig) Client {
+	return NewConfigurer(host, config).Create()
+}
+
+// NewClientWithTimeout returns a new scp.Client with provides host, ssh.ClientConfig and timeout.
+// Deprecated: provide meaningful context to each "Copy*" function instead.
+func NewClientWithTimeout(host string, config *ssh.ClientConfig, timeout time.Duration) Client {
+	return NewConfigurer(host, config).Timeout(timeout).Create()
+}
+
+// NewClientBySSH returns a new scp.Client using an already existing established SSH connection.
+func NewClientBySSH(ssh *ssh.Client) (Client, error) {
+	session, err := ssh.NewSession()
+	if err != nil {
+		return Client{}, err
+	}
+	return NewConfigurer("", nil).Session(session).Create(), nil
+}
+
+// NewClientBySSHWithTimeout same as NewClientWithTimeout but uses an existing SSH client.
+// Deprecated: provide meaningful context to each "Copy*" function instead.
+func NewClientBySSHWithTimeout(ssh *ssh.Client, timeout time.Duration) (Client, error) {
+	session, err := ssh.NewSession()
+	if err != nil {
+		return Client{}, err
+	}
+	return NewConfigurer("", nil).Session(session).Timeout(timeout).Create(), nil
+}
diff --git a/tests/basic_test.go b/tests/basic_test.go
new file mode 100644
index 0000000..66cce46
--- /dev/null
+++ b/tests/basic_test.go
@@ -0,0 +1,191 @@
+package tests_test
+
+import (
+	"context"
+	"io/ioutil"
+	"os"
+	"strings"
+	"testing"
+	"time"
+
+	"google3/third_party/golang/github_com/bramvdbogaerde/go_scp/v/v1/auth/auth"
+	"google3/third_party/golang/github_com/bramvdbogaerde/go_scp/v/v1/scp"
+	"google3/third_party/golang/go_crypto/ssh/ssh"
+)
+
+func establishConnection(t *testing.T) scp.Client {
+	// Use SSH key authentication from the auth package.
+	// During testing we ignore the host key, don't to that when you use this.
+	clientConfig, _ := auth.PasswordKey("bram", "test", ssh.InsecureIgnoreHostKey())
+
+	// Create a new SCP client.
+	client := scp.NewClient("127.0.0.1:2244", &clientConfig)
+
+	// Connect to the remote server.
+	err := client.Connect()
+	if err != nil {
+		t.Fatalf("Couldn't establish a connection to the remote server: %s", err)
+	}
+	return client
+}
+
+// TestCopy tests the basic functionality of copying a file to the remote
+// destination.
+//
+// It assumes that a Docker container is running an SSH server at port 2244
+// that is using password authentication. It also assumes that the directory
+// /data is writable within that container and is mapped to ./tmp/ within the
+// directory the test is run from.
+func TestCopy(t *testing.T) {
+	client := establishConnection(t)
+	defer client.Close()
+
+	// Open a file we can transfer to the remote container.
+	f, _ := os.Open("./data/upload_file.txt")
+	defer f.Close()
+
+	// Create a file name with exotic characters and spaces in them.
+	// If this test works for this, simpler files should not be a problem.
+	filename := "Exöt1ç uploaded file.txt"
+
+	// Finaly, copy the file over.
+	// Usage: CopyFile(fileReader, remotePath, permission).
+	err := client.CopyFile(context.Background(), f, "/data/"+filename, "0777")
+	if err != nil {
+		t.Errorf("Error while copying file: %s", err)
+	}
+
+	// Read what the receiver have written to disk.
+	content, err := ioutil.ReadFile("./tmp/" + filename)
+	if err != nil {
+		t.Errorf("Result file could not be read: %s", err)
+	}
+
+	text := string(content)
+	expected := "It Works\n"
+	if strings.Compare(text, expected) != 0 {
+		t.Errorf("Got different text than expected, expected %q got, %q", expected, text)
+	}
+}
+
+// TestDownloadFile tests the basic functionality of copying a file from the
+// remote destination.
+//
+// It assumes that a Docker container is running an SSH server at port 2244
+// that is using password authentication. It also assumes that the directory
+// /data is writable within that container and is mapped to ./tmp/ within the
+// directory the test is run from.
+func TestDownloadFile(t *testing.T) {
+	client := establishConnection(t)
+	defer client.Close()
+
+	// Open a file we can transfer to the remote container.
+	f, _ := os.Open("./data/input.txt")
+	defer f.Close()
+
+	// Create a local file to write to.
+	f, err := os.OpenFile("./tmp/output.txt", os.O_RDWR|os.O_CREATE, 0777)
+	if err != nil {
+		t.Errorf("Couldn't open the output file")
+	}
+	defer f.Close()
+
+	// Use a file name with exotic characters and spaces in them.
+	// If this test works for this, simpler files should not be a problem.
+	err = client.CopyFromRemote(context.Background(), f, "/input/Exöt1ç download file.txt.txt")
+	if err != nil {
+		t.Errorf("Copy failed from remote: %s", err.Error())
+	}
+
+	content, err := ioutil.ReadFile("./tmp/output.txt")
+	if err != nil {
+		t.Errorf("Result file could not be read: %s", err)
+	}
+
+	text := string(content)
+	expected := "It works for download!\n"
+	if strings.Compare(text, expected) != 0 {
+		t.Errorf("Got different text than expected, expected %q got, %q", expected, text)
+	}
+}
+
+// TestTimeoutDownload tests that a timeout error is produced if the file is not copied in the given
+// amount of time.
+func TestTimeoutDownload(t *testing.T) {
+	client := establishConnection(t)
+	defer client.Close()
+	client.Timeout = 1 * time.Millisecond
+
+	// Open a file we can transfer to the remote container.
+	f, _ := os.Open("./data/upload_file.txt")
+	defer f.Close()
+
+	// Create a file name with exotic characters and spaces in them.
+	// If this test works for this, simpler files should not be a problem.
+	filename := "Exöt1ç uploaded file.txt"
+
+	err := client.CopyFile(context.Background(), f, "/data/"+filename, "0777")
+	if err != context.DeadlineExceeded {
+		t.Errorf("Expected a timeout error but got succeeded without error")
+	}
+}
+
+// TestContextCancelDownload tests that a a copy is immediately cancelled if we call context.cancel()
+func TestContextCancelDownload(t *testing.T) {
+	client := establishConnection(t)
+	defer client.Close()
+
+	ctx, cancel := context.WithCancel(context.Background())
+	cancel()
+
+	// Open a file we can transfer to the remote container.
+	f, _ := os.Open("./data/upload_file.txt")
+	defer f.Close()
+
+	// Create a file name with exotic characters and spaces in them.
+	// If this test works for this, simpler files should not be a problem.
+	filename := "Exöt1ç uploaded file.txt"
+
+	err := client.CopyFile(ctx, f, "/data/"+filename, "0777")
+	if err != context.Canceled {
+		t.Errorf("Expected a canceled error but transfer succeeded without error")
+	}
+}
+
+func TestDownloadBadLocalFilePermissions(t *testing.T) {
+	client := establishConnection(t)
+	defer client.Close()
+
+	// Create a file with local bad permissions
+	// This happens only on Linux
+	f, err := os.OpenFile("./tmp/output_bdf.txt", os.O_CREATE, 0644)
+	if err != nil {
+		t.Error("Couldn't open the output file", err.Error())
+	}
+	defer f.Close()
+
+	// This should not timeout and throw an error
+	err = client.CopyFromRemote(context.Background(), f, "/input/Exöt1ç download file.txt.txt")
+	if err == nil {
+		t.Errorf("Expected error thrown. Got nil")
+	}
+}
+
+func TestFileNotFound(t *testing.T) {
+	client := establishConnection(t)
+	defer client.Close()
+
+	f, err := os.OpenFile("./tmp/output_fnf.txt", os.O_CREATE|os.O_WRONLY, 0644)
+	if err != nil {
+		t.Error("Couldn't open the output file", err.Error())
+	}
+	// This should throw file not found on remote
+	err = client.CopyFromRemote(context.Background(), f, "/input/no_such_file.txt")
+	if err == nil {
+		t.Errorf("Expected error thrown. Got nil")
+	}
+	expected := "scp: /input/no_such_file.txt: No such file or directory\n"
+	if err.Error() != expected {
+		t.Errorf("Expected %v, got %v", expected, err.Error())
+	}
+}
diff --git "a/tests/data/Ex\303\266t1\303\247_download_file.txt.txt" "b/tests/data/Ex\303\266t1\303\247_download_file.txt.txt"
new file mode 100644
index 0000000..7f191e8
--- /dev/null
+++ "b/tests/data/Ex\303\266t1\303\247_download_file.txt.txt"
@@ -0,0 +1 @@
+It works for download!
diff --git a/tests/data/upload_file.txt b/tests/data/upload_file.txt
new file mode 100644
index 0000000..2bfa722
--- /dev/null
+++ b/tests/data/upload_file.txt
@@ -0,0 +1 @@
+It Works
diff --git a/tests/entrypoint_d/setpasswd.sh b/tests/entrypoint_d/setpasswd.sh
new file mode 100644
index 0000000..dc6a15d
--- /dev/null
+++ b/tests/entrypoint_d/setpasswd.sh
@@ -0,0 +1,3 @@
+#!/usr/bin/env bash
+
+echo 'bram:$6$9CLVhdvYPsRYjJZJ$UoJbmXrl6F.kL1u1Mnio6gRgKAB7HG0eSeATa.HMu7liRGAINTicM0Ql5/AONVwKXsrA0BbMOOr3BHrODnP2s0' | chpasswd --encrypted
diff --git a/tests/run_all.sh b/tests/run_all.sh
new file mode 100644
index 0000000..834c78e
--- /dev/null
+++ b/tests/run_all.sh
@@ -0,0 +1,29 @@
+#!/usr/bin/env bash
+
+rm tmp/*
+
+echo "Running from $(pwd)"
+
+echo "Starting docker containers"
+
+docker run -d \
+   --name go-scp-test \
+   -p 2244:22 \
+   -e SSH_USERS=bram:1000:1000 \
+   -e SSH_ENABLE_PASSWORD_AUTH=true \
+   -v $(pwd)/tmp:/data/  \
+   -v $(pwd)/data:/input  \
+   -v $(pwd)/entrypoint.d/:/etc/entrypoint.d/ \
+   panubo/sshd
+
+sleep 5
+
+echo "Running tests"
+go test -v 
+
+echo "Tearing down docker containers"
+docker stop go-scp-test
+docker rm go-scp-test
+
+echo "Cleaning up"
+rm tmp/*
diff --git a/utils.go b/utils.go
new file mode 100644
index 0000000..447775f
--- /dev/null
+++ b/utils.go
@@ -0,0 +1,25 @@
+/* Copyright (c) 2021 Bram Vandenbogaerde And Contributors
+ * You may use, distribute or modify this code under the
+ * terms of the Mozilla Public License 2.0, which is distributed
+ * along with the source code.
+ */
+
+package scp
+
+import "io"
+
+// CopyN an adaptation of io.CopyN that keeps reading if it did not return
+// a sufficient amount of bytes.
+func CopyN(writer io.Writer, src io.Reader, size int64) (int64, error) {
+	var total int64
+	total = 0
+	for total < size {
+		n, err := io.CopyN(writer, src, size)
+		if err != nil {
+			return 0, err
+		}
+		total += n
+	}
+
+	return total, nil
+}