Love Letter to the Cloud
I discovered an undocumented feature in my wifi-capable, portable document scanner: It can upload scanned documents, such as unsolicited love letters, via a webhook. Implementing the server side of the webhook was a nice afternoon project.

First I created a function to handle the request:
func Webhook(w http.ResponseWriter, r *http.Request) {
...
}
Then I didn’t want spies and wiresharkers looking at my top secret scans of love letters. I receive approximately 10 a month, by the way.
func Webhook(w http.ResponseWriter, r *http.Request) {
// check credentials
user, pass, ok := r.BasicAuth()
if !ok ||
user != "foobar" ||
pass != "Mb2.r5oHf-0t") {
w.Header().Set("WWW-Authenticate", `Basic realm="scanner"`)
w.WriteHeader(401)
w.Write([]byte("unauthorised\n"))
return
}
...
Although convenient, storing passwords in source code is a no-no says the Internet. So I hashed the passwords but passed on the salt. Ain’t nobody got time for that!
var (
authorizedUserHash = []byte{0xd5, 0x65, 0x3c, 0x50, ..., 0x8b, 0xa2}
authorizedPassHash = []byte{0xc5, 0x24, 0x1e, 0xac, ..., 0x88, 0x7e}
)
Moving on I used SeCuRe authentication.
func Webhook(w http.ResponseWriter, r *http.Request) {
// check credentials
user, pass, ok := r.BasicAuth()
userHash := sha512.Sum512([]byte(user))
passHash := sha512.Sum512([]byte(pass))
if !ok ||
!bytes.Equal(userHash[:], authorizedUserHash) ||
!bytes.Equal(passHash[:], authorizedPassHash) {
...
}
...
Every time I scan one of the approximately 23 love letters I find on my doorstep every month, the scanner calls the webhook trying to upload a JPEG good ol’ multipart-form style. The function parses the first 10MiB of the incoming data to memory. The rest goes to disk or whatever. Ain’t nobody got space for that.
// parse uploaded file
r.ParseMultipartForm(10 << 20)
file, handler, err := r.FormFile("file")
if err != nil {
handleError(w, "retrieve uploaded file", err)
return
}
defer file.Close()
When uploading vast amounts of love letter scans (approximately 42 a month) the network occasionally eats up one or two. I introduced an error handling function to keep things handy.
func handleError(w http.ResponseWriter, info string, err error) {
msg := fmt.Sprintf("%s: %v\n", info, err)
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(msg))
log.Printf(msg)
}
Let’s proceed assuming the file was successfully received. I like to organize incoming love letters, of which I have to deal with up to 386 per month, in my online drive. From there I can move them to final processing at will. Consequently, my webhook function shall store the corresponding images in a dedicated folder within my online drive.
// store file in drive
service, err := drive.NewService(context.Background())
...
_, err = service.Files.Create(&drive.File{
Name: time.Now().Format("2006-01-02T15-04-05") + "_" + handler.Filename,
Parents: []string{"drive-folder-id-12345"}},
).Media(file).Do()
if err != nil {
handleError(w, "create file", err)
return
}
From the URL of the target folder in drive I extracted the folder ID like a real hacker. Good HTTP citizens behave friendly. Wanting to be a good HTTP citizen I explicitly send back a status code and message.
// all good
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write([]byte("{ \"status\": \"ok\" }\n"))
I then took my function and threw it into the cloud for added magic. First I wanted to build a docker container but then I remembered Cloud Functions. That saved me a lot of CI/CD pipeline hassle. Copy and paste was all the pipeline I needed.

Being on the receiving end of what amounts to 402 love letters per month I decided to better be safe than sorry and allow up to 3 concurrent instances. I can afford to lose a few but not all of them.

In my drive I had to share the target folder with the function’s service account.

I figured that 128MiB ought to be enough for everyone’s Cloud Function. Even high resolution scans don’t come close to hitting the limit.

That was fun! Now I don’t have to use an USB cable for connecting my document scanner to my computer. Every time I scan one of my 500+ monthly love letters I simply pipe the data through someone else’s computer.

Full Source
The function’s package p.go
:
package p
import (
"bytes"
"context"
"crypto/sha512"
"fmt"
"log"
"net/http"
"time"
"google.golang.org/api/drive/v3"
)
var (
authorizedUserHash = []byte{0xd5, 0x65, 0x3c, ..., 0x8b, 0xa2}
authorizedPassHash = []byte{0xc5, 0x24, 0x1e, ..., 0x88, 0x7e}
)
func handleError(w http.ResponseWriter, info string, err error) {
msg := fmt.Sprintf("%s: %v\n", info, err)
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(msg))
log.Printf(msg)
}
func Webhook(w http.ResponseWriter, r *http.Request) {
// check credentials
user, pass, ok := r.BasicAuth()
userHash := sha512.Sum512([]byte(user))
passHash := sha512.Sum512([]byte(pass))
if !ok ||
!bytes.Equal(userHash[:], authorizedUserHash) ||
!bytes.Equal(passHash[:], authorizedPassHash) {
w.Header().Set("WWW-Authenticate", `Basic realm="scanner"`)
w.WriteHeader(401)
w.Write([]byte("unauthorised\n"))
return
}
// parse uploaded file
r.ParseMultipartForm(10 << 20)
file, handler, err := r.FormFile("file")
if err != nil {
handleError(w, "retrieve uploaded file", err)
return
}
defer file.Close()
// store file in drive
service, err := drive.NewService(context.Background())
if err != nil {
handleError(w, "new service", err)
return
}
_, err = service.Files.Create(&drive.File{
Name: time.Now().Format("2006-01-02T15-04-05") + "_" + handler.Filename,
Parents: []string{"folder-id-12345"}},
).Media(file).Do()
if err != nil {
handleError(w, "create file", err)
return
}
// all good
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write([]byte("{ \"status\": \"ok\" }\n"))
}
Corresponding go.mod
module cloudfunction
require google.golang.org/api v0.25.0