Attempting to Learn Go - Building a Downloader Part 05
Tags: go, continuingeducation, atlg
Published: 2018-01-19 19:25:27

---
title: "Attempting to Learn Go - Building a Downloader Part 05"
published: true
tags: go,golang,continuing-education,atlg
canonical_url: https://shindakun.glitch.me/attempting-to-learn-go-building-a-downloader-part-05/
cover_image: https://shindakun.glitch.me/content/images/2018/01/attemptinggo.png
series: "Attempting to Learn Go"
---
This post is going to be a bit different than the [previous](https://dev.to/shindakun/attempting-to-learn-go---building-a-downloader-part-04-3ln9) ones. We're only going to make a small update to our Go code. This is mainly to take care of some of the sanitization worries I pointed out last time. Once we're done with that we'll layout the code we need to create an extension for Chrome! And on that note lets get going!

## Pass Six

We're going to start by adding in the [Sanitize](https://shindakun.glitch.me/attempting-to-learn-go-building-a-downloader-part-05/github.com/kennygrant/sanitize) package by `kennygrant`. To add a package in Go we need to use `go get` from the command line.

```bash
go get github.com/kennygrant/sanitize
```

We can then add it to our import list as shown in the code below. Sanitize adds functions which will help in sanitizing text strings. Exactly what we are looking for! This should help remove any unwanted characters from the files and directories we attempt to create.

Small update, as @rafaacioly points out, I can use the built-in `http.StatusBadRequest` and `http.StatusInternalServerError` on my HTTP errors. It's more characters but I believe it's "correct" to use the built-in values.

```go
package main

import (  
  "encoding/json"
  "fmt"
  "io"
  "io/ioutil"
  "log"
  "net/http"
  "net/url"
  "os"
  "path/filepath"
  "strings"

  "github.com/kennygrant/sanitize"
)

type download struct {  
  Title string `json:"title"`
  Location string `json:"location"`
}

func status(response http.ResponseWriter, request *http.Request) {  
  fmt.Fprintf(response, "Hello!")
}

func handleDownloadRequest(response http.ResponseWriter, request *http.Request) {  
  var downloadRequest download
  r, err := ioutil.ReadAll(request.Body)
  if err != nil {
    http.Error(response, "bad request", http.StatusBadRequest)
    log.Println(err)
    return
  }
  defer request.Body.Close()

  err = json.Unmarshal(r, &downloadRequest)
  if err != nil {
    http.Error(response, "bad request: "+err.Error(), http.StatusBadRequest)
    return
  }
  log.Printf("%#v", downloadRequest)

  err = getFile(downloadRequest)
  if err != nil {
    http.Error(response, "internal server error: "+err.Error(), http.StatusInternalServerError)
    return
  }

  fmt.Fprintf(response, "Download!")
}

func createSaveDirectory(title string) (string, error) {  
  dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
  if err != nil {
    log.Fatal(err)
  }
  path := filepath.Join(dir, title)
  _, err = os.Stat(path)

  // directory does not exist we should create it.
  if err != nil {
    err = os.Mkdir(path, 0755)
    if err != nil {
      log.Fatal(err)
    }
  }

  return path, nil
}
```

As you can see below, we are calling `sanitize` in two places. The first, `sanitize.BaseName`, is when passing the title to the `createSaveDirectory` function. The second, `sanitize.Path`, when attempting to actually save the file with `os.Create`. The part should take care of removing any characters we don't want. For instance, if someone tries a directory transversal with the title by submitting the JSON `"title": "Fake Title ../../../../../etc/"`. The save directory will still be created as a subfolder where our application resides, it will just have the name `Fake-Title-etc-`. The filename is sanitized slightly differently but the end result is basically the same. I didn't do an exhaustive test but for now, we should be OK.

```go
func getFile(downloadRequest download) error {  
  u, err := url.Parse(downloadRequest.Location)
  if err != nil {
    log.Println(err)
    return err
  }

  save, err := createSaveDirectory(sanitize.BaseName(downloadRequest.Title))
  if err != nil {
    log.Println(err)
    return err
  }

  // Encoding URL via path never seems to work as expected, fall back to
  // simply replacing spaces with %20's, for now.
  response, err := http.Get(strings.Replace(downloadRequest.Location, " ", "%20", -1))
  if err != nil {
    log.Println(err)
    return err
  }
  defer response.Body.Close()

  out, err := os.Create(filepath.Join(save, sanitize.Path(filepath.Base(u.Path))))
  defer out.Close()
  _, err = io.Copy(out, response.Body)
  if err != nil {
    log.Println(err)
    return err
  }
  return nil
}

func main() {  
  log.Println("Downloader")

  http.HandleFunc("/", status)
  http.HandleFunc("/download", handleDownloadRequest)
  http.ListenAndServe(":3000", nil)
}
```

That wraps up the Go portion of this post. Now, we're going to do a crash course in writing Chrome extensions! I'm not going to go too deep and for now, will only cover the three core files that make up the extension. For a deeper look at creating an extension check out the [extension section](https://developer.chrome.com/extensions/getstarted) on the Chrome developers portal.

* * *

## Chrome Extension

An extension is typically made up of three or four files. The `manifest.json`, is the most important, it provides the browser with all the information it needs to load the extension. The permissions and the background entry are probably the most important sections within the file to note for now. Also, note that we are using `localhost` for our testing.

```json
{
  "manifest_version": 2,
  "name": "Send to Downloader",
  "short_name": "Send2DL",
  "description": "Sends JSON to download server.",
  "version": "0.0.1",
  "minimum_chrome_version": "38",
  "permissions": [
    "webRequest",
    "*://localhost/",
    "contextMenus",
    "activeTab"],
  "icons": {
    "16": "assets/download.png"
  },
  "background": {"page": "background.html"}
}
```

I'm sure you are surprised to see our `background.html` below. That's right it doesn't do much of anything, except load `main.js`, which is actually a pretty big deal.

```html
<!DOCTYPE html>  
<html>  
  <body>
    <script src="main.js"></script>
  </body>
</html>
```

`main.js` is the heart of our extension, as it actually takes care of the of putting our request together. It also handles adding our download option to Chrome's right-click context menu. Looking close you can see that the "Send to downloader" option will only appear if you right click on an image. In its current form, the code simply takes the title of the current tab and uses that as the directory you want to save the image in. If I ever take this past a learning exercise I'd like to extend this to any link and file type.

```javascript
sendToDownloader = function(obj, tab) {  
  let downloaderUrl = 'http://localhost:3000/download';

  let title = tab.title;
  let imgUrl = obj.srcUrl;

  let payload = {
    title: title,
    location: imgUrl
  };

  let xhr = new XMLHttpRequest();

  xhr.open("POST", downloaderUrl, false);
  xhr.setRequestHeader('Content-type','application/json; charset=utf-8');
  xhr.send(JSON.stringify(payload));

  let result = xhr.responseText;
};

chrome.contextMenus.create({  
  title: "Send to downloader",
  contexts:["image"],
  onclick: sendToDownloader
});
```

That brings us almost to the end of this exercise. The final post will cover the process of actually deploying the application on Google Compute engine. We'll finally be able to see the whole thing in action! After that, I may poke around with the downloader further but, I probably won't document too much outside of what is committed to [GitHub](https://github.com/shindakun/downloader).

Until next time...

***

You can find the code for this and most of the other Attempting to Learn Go posts in the repo on GitHub.

{% github shindakun/atlg %}

***

| Enjoy this post? |
|:----:|
| [How about buying me a coffee?](https://ko-fi.com/shindakun) |

***