1 // Package v4 implements signing for AWS V4 signer
3 // Provides request signing for request that need to be signed with
8 // Generally using the signer outside of the SDK should not require any additional
9 // logic when using Go v1.5 or higher. The signer does this by taking advantage
10 // of the URL.EscapedPath method. If your request URI requires additional escaping
11 // you many need to use the URL.Opaque to define what the raw URI should be sent
14 // The signer will first check the URL.Opaque field, and use its value if set.
15 // The signer does require the URL.Opaque field to be set in the form of:
17 // "//<hostname>/<path>"
20 // "//example.com/some/path"
22 // The leading "//" and hostname are required or the URL.Opaque escaping will
23 // not work correctly.
25 // If URL.Opaque is not set the signer will fallback to the URL.EscapedPath()
26 // method and using the returned value. If you're using Go v1.4 you must set
27 // URL.Opaque if the URI path needs escaping. If URL.Opaque is not set with
28 // Go v1.5 the signer will fallback to URL.Path.
30 // AWS v4 signature validation requires that the canonical string's URI path
31 // element must be the URI escaped form of the HTTP request's path.
32 // http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
34 // The Go HTTP client will perform escaping automatically on the request. Some
35 // of these escaping may cause signature validation errors because the HTTP
36 // request differs from the URI path or query that the signature was generated.
37 // https://golang.org/pkg/net/url/#URL.EscapedPath
39 // Because of this, it is recommended that when using the signer outside of the
40 // SDK that explicitly escaping the request prior to being signed is preferable,
41 // and will help prevent signature validation errors. This can be done by setting
42 // the URL.Opaque or URL.RawPath. The SDK will use URL.Opaque first and then
43 // call URL.EscapedPath() if Opaque is not set.
45 // If signing a request intended for HTTP2 server, and you're using Go 1.6.2
46 // through 1.7.4 you should use the URL.RawPath as the pre-escaped form of the
47 // request URL. https://github.com/golang/go/issues/16847 points to a bug in
48 // Go pre 1.8 that fails to make HTTP2 requests using absolute URL in the HTTP
49 // message. URL.Opaque generally will force Go to make requests with absolute URL.
50 // URL.RawPath does not do this, but RawPath must be a valid escaping of Path
51 // or url.EscapedPath will ignore the RawPath escaping.
53 // Test `TestStandaloneSign` provides a complete example of using the signer
54 // outside of the SDK and pre-escaping the URI path.
71 "github.com/aws/aws-sdk-go/aws"
72 "github.com/aws/aws-sdk-go/aws/credentials"
73 "github.com/aws/aws-sdk-go/aws/request"
74 "github.com/aws/aws-sdk-go/internal/sdkio"
75 "github.com/aws/aws-sdk-go/private/protocol/rest"
79 authHeaderPrefix = "AWS4-HMAC-SHA256"
80 timeFormat = "20060102T150405Z"
81 shortTimeFormat = "20060102"
83 // emptyStringSHA256 is a SHA256 of an empty string
84 emptyStringSHA256 = `e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855`
87 var ignoredHeaders = rules{
90 "Authorization": struct{}{},
91 "User-Agent": struct{}{},
92 "X-Amzn-Trace-Id": struct{}{},
97 // requiredSignedHeaders is a whitelist for build canonical headers.
98 var requiredSignedHeaders = rules{
101 "Cache-Control": struct{}{},
102 "Content-Disposition": struct{}{},
103 "Content-Encoding": struct{}{},
104 "Content-Language": struct{}{},
105 "Content-Md5": struct{}{},
106 "Content-Type": struct{}{},
107 "Expires": struct{}{},
108 "If-Match": struct{}{},
109 "If-Modified-Since": struct{}{},
110 "If-None-Match": struct{}{},
111 "If-Unmodified-Since": struct{}{},
113 "X-Amz-Acl": struct{}{},
114 "X-Amz-Copy-Source": struct{}{},
115 "X-Amz-Copy-Source-If-Match": struct{}{},
116 "X-Amz-Copy-Source-If-Modified-Since": struct{}{},
117 "X-Amz-Copy-Source-If-None-Match": struct{}{},
118 "X-Amz-Copy-Source-If-Unmodified-Since": struct{}{},
119 "X-Amz-Copy-Source-Range": struct{}{},
120 "X-Amz-Copy-Source-Server-Side-Encryption-Customer-Algorithm": struct{}{},
121 "X-Amz-Copy-Source-Server-Side-Encryption-Customer-Key": struct{}{},
122 "X-Amz-Copy-Source-Server-Side-Encryption-Customer-Key-Md5": struct{}{},
123 "X-Amz-Grant-Full-control": struct{}{},
124 "X-Amz-Grant-Read": struct{}{},
125 "X-Amz-Grant-Read-Acp": struct{}{},
126 "X-Amz-Grant-Write": struct{}{},
127 "X-Amz-Grant-Write-Acp": struct{}{},
128 "X-Amz-Metadata-Directive": struct{}{},
129 "X-Amz-Mfa": struct{}{},
130 "X-Amz-Request-Payer": struct{}{},
131 "X-Amz-Server-Side-Encryption": struct{}{},
132 "X-Amz-Server-Side-Encryption-Aws-Kms-Key-Id": struct{}{},
133 "X-Amz-Server-Side-Encryption-Customer-Algorithm": struct{}{},
134 "X-Amz-Server-Side-Encryption-Customer-Key": struct{}{},
135 "X-Amz-Server-Side-Encryption-Customer-Key-Md5": struct{}{},
136 "X-Amz-Storage-Class": struct{}{},
137 "X-Amz-Website-Redirect-Location": struct{}{},
138 "X-Amz-Content-Sha256": struct{}{},
141 patterns{"X-Amz-Meta-"},
144 // allowedHoisting is a whitelist for build query headers. The boolean value
145 // represents whether or not it is a pattern.
146 var allowedQueryHoisting = inclusiveRules{
147 blacklist{requiredSignedHeaders},
151 // Signer applies AWS v4 signing to given request. Use this to sign requests
152 // that need to be signed with AWS V4 Signatures.
154 // The authentication credentials the request will be signed against.
155 // This value must be set to sign requests.
156 Credentials *credentials.Credentials
158 // Sets the log level the signer should use when reporting information to
159 // the logger. If the logger is nil nothing will be logged. See
160 // aws.LogLevelType for more information on available logging levels
162 // By default nothing will be logged.
163 Debug aws.LogLevelType
165 // The logger loging information will be written to. If there the logger
166 // is nil, nothing will be logged.
169 // Disables the Signer's moving HTTP header key/value pairs from the HTTP
170 // request header to the request's query string. This is most commonly used
171 // with pre-signed requests preventing headers from being added to the
172 // request's query string.
173 DisableHeaderHoisting bool
175 // Disables the automatic escaping of the URI path of the request for the
176 // siganture's canonical string's path. For services that do not need additional
177 // escaping then use this to disable the signer escaping the path.
179 // S3 is an example of a service that does not need additional escaping.
181 // http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
182 DisableURIPathEscaping bool
184 // Disales the automatical setting of the HTTP request's Body field with the
185 // io.ReadSeeker passed in to the signer. This is useful if you're using a
186 // custom wrapper around the body for the io.ReadSeeker and want to preserve
187 // the Body value on the Request.Body.
189 // This does run the risk of signing a request with a body that will not be
190 // sent in the request. Need to ensure that the underlying data of the Body
191 // values are the same.
192 DisableRequestBodyOverwrite bool
194 // currentTimeFn returns the time value which represents the current time.
195 // This value should only be used for testing. If it is nil the default
196 // time.Now will be used.
197 currentTimeFn func() time.Time
199 // UnsignedPayload will prevent signing of the payload. This will only
200 // work for services that have support for this.
204 // NewSigner returns a Signer pointer configured with the credentials and optional
205 // option values provided. If not options are provided the Signer will use its
206 // default configuration.
207 func NewSigner(credentials *credentials.Credentials, options ...func(*Signer)) *Signer {
209 Credentials: credentials,
212 for _, option := range options {
219 type signingCtx struct {
222 Request *http.Request
226 ExpireTime time.Duration
227 SignedHeaderVals http.Header
229 DisableURIPathEscaping bool
231 credValues credentials.Value
234 formattedShortTime string
239 canonicalHeaders string
240 canonicalString string
241 credentialString string
247 // Sign signs AWS v4 requests with the provided body, service name, region the
248 // request is made to, and time the request is signed at. The signTime allows
249 // you to specify that a request is signed for the future, and cannot be
252 // Returns a list of HTTP headers that were included in the signature or an
253 // error if signing the request failed. Generally for signed requests this value
254 // is not needed as the full request context will be captured by the http.Request
255 // value. It is included for reference though.
257 // Sign will set the request's Body to be the `body` parameter passed in. If
258 // the body is not already an io.ReadCloser, it will be wrapped within one. If
259 // a `nil` body parameter passed to Sign, the request's Body field will be
260 // also set to nil. Its important to note that this functionality will not
261 // change the request's ContentLength of the request.
263 // Sign differs from Presign in that it will sign the request using HTTP
264 // header values. This type of signing is intended for http.Request values that
265 // will not be shared, or are shared in a way the header values on the request
268 // The requests body is an io.ReadSeeker so the SHA256 of the body can be
269 // generated. To bypass the signer computing the hash you can set the
270 // "X-Amz-Content-Sha256" header with a precomputed value. The signer will
271 // only compute the hash if the request header value is empty.
272 func (v4 Signer) Sign(r *http.Request, body io.ReadSeeker, service, region string, signTime time.Time) (http.Header, error) {
273 return v4.signWithBody(r, body, service, region, 0, false, signTime)
276 // Presign signs AWS v4 requests with the provided body, service name, region
277 // the request is made to, and time the request is signed at. The signTime
278 // allows you to specify that a request is signed for the future, and cannot
279 // be used until then.
281 // Returns a list of HTTP headers that were included in the signature or an
282 // error if signing the request failed. For presigned requests these headers
283 // and their values must be included on the HTTP request when it is made. This
284 // is helpful to know what header values need to be shared with the party the
285 // presigned request will be distributed to.
287 // Presign differs from Sign in that it will sign the request using query string
288 // instead of header values. This allows you to share the Presigned Request's
289 // URL with third parties, or distribute it throughout your system with minimal
292 // Presign also takes an exp value which is the duration the
293 // signed request will be valid after the signing time. This is allows you to
294 // set when the request will expire.
296 // The requests body is an io.ReadSeeker so the SHA256 of the body can be
297 // generated. To bypass the signer computing the hash you can set the
298 // "X-Amz-Content-Sha256" header with a precomputed value. The signer will
299 // only compute the hash if the request header value is empty.
301 // Presigning a S3 request will not compute the body's SHA256 hash by default.
302 // This is done due to the general use case for S3 presigned URLs is to share
303 // PUT/GET capabilities. If you would like to include the body's SHA256 in the
304 // presigned request's signature you can set the "X-Amz-Content-Sha256"
305 // HTTP header and that will be included in the request's signature.
306 func (v4 Signer) Presign(r *http.Request, body io.ReadSeeker, service, region string, exp time.Duration, signTime time.Time) (http.Header, error) {
307 return v4.signWithBody(r, body, service, region, exp, true, signTime)
310 func (v4 Signer) signWithBody(r *http.Request, body io.ReadSeeker, service, region string, exp time.Duration, isPresign bool, signTime time.Time) (http.Header, error) {
311 currentTimeFn := v4.currentTimeFn
312 if currentTimeFn == nil {
313 currentTimeFn = time.Now
319 Query: r.URL.Query(),
322 isPresign: isPresign,
323 ServiceName: service,
325 DisableURIPathEscaping: v4.DisableURIPathEscaping,
326 unsignedPayload: v4.UnsignedPayload,
329 for key := range ctx.Query {
330 sort.Strings(ctx.Query[key])
333 if ctx.isRequestSigned() {
334 ctx.Time = currentTimeFn()
335 ctx.handlePresignRemoval()
339 ctx.credValues, err = v4.Credentials.Get()
341 return http.Header{}, err
344 ctx.sanitizeHostForHeader()
345 ctx.assignAmzQueryValues()
346 if err := ctx.build(v4.DisableHeaderHoisting); err != nil {
350 // If the request is not presigned the body should be attached to it. This
351 // prevents the confusion of wanting to send a signed request without
352 // the body the request was signed for attached.
353 if !(v4.DisableRequestBodyOverwrite || ctx.isPresign) {
354 var reader io.ReadCloser
357 if reader, ok = body.(io.ReadCloser); !ok {
358 reader = ioutil.NopCloser(body)
364 if v4.Debug.Matches(aws.LogDebugWithSigning) {
365 v4.logSigningInfo(ctx)
368 return ctx.SignedHeaderVals, nil
371 func (ctx *signingCtx) sanitizeHostForHeader() {
372 request.SanitizeHostForHeader(ctx.Request)
375 func (ctx *signingCtx) handlePresignRemoval() {
380 // The credentials have expired for this request. The current signing
381 // is invalid, and needs to be request because the request will fail.
384 // Update the request's query string to ensure the values stays in
385 // sync in the case retrieving the new credentials fails.
386 ctx.Request.URL.RawQuery = ctx.Query.Encode()
389 func (ctx *signingCtx) assignAmzQueryValues() {
391 ctx.Query.Set("X-Amz-Algorithm", authHeaderPrefix)
392 if ctx.credValues.SessionToken != "" {
393 ctx.Query.Set("X-Amz-Security-Token", ctx.credValues.SessionToken)
395 ctx.Query.Del("X-Amz-Security-Token")
401 if ctx.credValues.SessionToken != "" {
402 ctx.Request.Header.Set("X-Amz-Security-Token", ctx.credValues.SessionToken)
406 // SignRequestHandler is a named request handler the SDK will use to sign
407 // service client request with using the V4 signature.
408 var SignRequestHandler = request.NamedHandler{
409 Name: "v4.SignRequestHandler", Fn: SignSDKRequest,
412 // SignSDKRequest signs an AWS request with the V4 signature. This
413 // request handler should only be used with the SDK's built in service client's
414 // API operation requests.
416 // This function should not be used on its on its own, but in conjunction with
417 // an AWS service client's API operation call. To sign a standalone request
418 // not created by a service client's API operation method use the "Sign" or
419 // "Presign" functions of the "Signer" type.
421 // If the credentials of the request's config are set to
422 // credentials.AnonymousCredentials the request will not be signed.
423 func SignSDKRequest(req *request.Request) {
424 signSDKRequestWithCurrTime(req, time.Now)
427 // BuildNamedHandler will build a generic handler for signing.
428 func BuildNamedHandler(name string, opts ...func(*Signer)) request.NamedHandler {
429 return request.NamedHandler{
431 Fn: func(req *request.Request) {
432 signSDKRequestWithCurrTime(req, time.Now, opts...)
437 func signSDKRequestWithCurrTime(req *request.Request, curTimeFn func() time.Time, opts ...func(*Signer)) {
438 // If the request does not need to be signed ignore the signing of the
439 // request if the AnonymousCredentials object is used.
440 if req.Config.Credentials == credentials.AnonymousCredentials {
444 region := req.ClientInfo.SigningRegion
446 region = aws.StringValue(req.Config.Region)
449 name := req.ClientInfo.SigningName
451 name = req.ClientInfo.ServiceName
454 v4 := NewSigner(req.Config.Credentials, func(v4 *Signer) {
455 v4.Debug = req.Config.LogLevel.Value()
456 v4.Logger = req.Config.Logger
457 v4.DisableHeaderHoisting = req.NotHoist
458 v4.currentTimeFn = curTimeFn
460 // S3 service should not have any escaping applied
461 v4.DisableURIPathEscaping = true
463 // Prevents setting the HTTPRequest's Body. Since the Body could be
464 // wrapped in a custom io.Closer that we do not want to be stompped
465 // on top of by the signer.
466 v4.DisableRequestBodyOverwrite = true
469 for _, opt := range opts {
473 signingTime := req.Time
474 if !req.LastSignedAt.IsZero() {
475 signingTime = req.LastSignedAt
478 signedHeaders, err := v4.signWithBody(req.HTTPRequest, req.GetBody(),
479 name, region, req.ExpireTime, req.ExpireTime > 0, signingTime,
483 req.SignedHeaderVals = nil
487 req.SignedHeaderVals = signedHeaders
488 req.LastSignedAt = curTimeFn()
491 const logSignInfoMsg = `DEBUG: Request Signature:
492 ---[ CANONICAL STRING ]-----------------------------
494 ---[ STRING TO SIGN ]--------------------------------
496 -----------------------------------------------------`
497 const logSignedURLMsg = `
498 ---[ SIGNED URL ]------------------------------------
501 func (v4 *Signer) logSigningInfo(ctx *signingCtx) {
504 signedURLMsg = fmt.Sprintf(logSignedURLMsg, ctx.Request.URL.String())
506 msg := fmt.Sprintf(logSignInfoMsg, ctx.canonicalString, ctx.stringToSign, signedURLMsg)
510 func (ctx *signingCtx) build(disableHeaderHoisting bool) error {
511 ctx.buildTime() // no depends
512 ctx.buildCredentialString() // no depends
514 if err := ctx.buildBodyDigest(); err != nil {
518 unsignedHeaders := ctx.Request.Header
520 if !disableHeaderHoisting {
521 urlValues := url.Values{}
522 urlValues, unsignedHeaders = buildQuery(allowedQueryHoisting, unsignedHeaders) // no depends
523 for k := range urlValues {
524 ctx.Query[k] = urlValues[k]
529 ctx.buildCanonicalHeaders(ignoredHeaders, unsignedHeaders)
530 ctx.buildCanonicalString() // depends on canon headers / signed headers
531 ctx.buildStringToSign() // depends on canon string
532 ctx.buildSignature() // depends on string to sign
535 ctx.Request.URL.RawQuery += "&X-Amz-Signature=" + ctx.signature
538 authHeaderPrefix + " Credential=" + ctx.credValues.AccessKeyID + "/" + ctx.credentialString,
539 "SignedHeaders=" + ctx.signedHeaders,
540 "Signature=" + ctx.signature,
542 ctx.Request.Header.Set("Authorization", strings.Join(parts, ", "))
548 func (ctx *signingCtx) buildTime() {
549 ctx.formattedTime = ctx.Time.UTC().Format(timeFormat)
550 ctx.formattedShortTime = ctx.Time.UTC().Format(shortTimeFormat)
553 duration := int64(ctx.ExpireTime / time.Second)
554 ctx.Query.Set("X-Amz-Date", ctx.formattedTime)
555 ctx.Query.Set("X-Amz-Expires", strconv.FormatInt(duration, 10))
557 ctx.Request.Header.Set("X-Amz-Date", ctx.formattedTime)
561 func (ctx *signingCtx) buildCredentialString() {
562 ctx.credentialString = strings.Join([]string{
563 ctx.formattedShortTime,
570 ctx.Query.Set("X-Amz-Credential", ctx.credValues.AccessKeyID+"/"+ctx.credentialString)
574 func buildQuery(r rule, header http.Header) (url.Values, http.Header) {
575 query := url.Values{}
576 unsignedHeaders := http.Header{}
577 for k, h := range header {
581 unsignedHeaders[k] = h
585 return query, unsignedHeaders
587 func (ctx *signingCtx) buildCanonicalHeaders(r rule, header http.Header) {
589 headers = append(headers, "host")
590 for k, v := range header {
591 canonicalKey := http.CanonicalHeaderKey(k)
592 if !r.IsValid(canonicalKey) {
593 continue // ignored header
595 if ctx.SignedHeaderVals == nil {
596 ctx.SignedHeaderVals = make(http.Header)
599 lowerCaseKey := strings.ToLower(k)
600 if _, ok := ctx.SignedHeaderVals[lowerCaseKey]; ok {
601 // include additional values
602 ctx.SignedHeaderVals[lowerCaseKey] = append(ctx.SignedHeaderVals[lowerCaseKey], v...)
606 headers = append(headers, lowerCaseKey)
607 ctx.SignedHeaderVals[lowerCaseKey] = v
609 sort.Strings(headers)
611 ctx.signedHeaders = strings.Join(headers, ";")
614 ctx.Query.Set("X-Amz-SignedHeaders", ctx.signedHeaders)
617 headerValues := make([]string, len(headers))
618 for i, k := range headers {
620 if ctx.Request.Host != "" {
621 headerValues[i] = "host:" + ctx.Request.Host
623 headerValues[i] = "host:" + ctx.Request.URL.Host
626 headerValues[i] = k + ":" +
627 strings.Join(ctx.SignedHeaderVals[k], ",")
630 stripExcessSpaces(headerValues)
631 ctx.canonicalHeaders = strings.Join(headerValues, "\n")
634 func (ctx *signingCtx) buildCanonicalString() {
635 ctx.Request.URL.RawQuery = strings.Replace(ctx.Query.Encode(), "+", "%20", -1)
637 uri := getURIPath(ctx.Request.URL)
639 if !ctx.DisableURIPathEscaping {
640 uri = rest.EscapePath(uri, false)
643 ctx.canonicalString = strings.Join([]string{
646 ctx.Request.URL.RawQuery,
647 ctx.canonicalHeaders + "\n",
653 func (ctx *signingCtx) buildStringToSign() {
654 ctx.stringToSign = strings.Join([]string{
657 ctx.credentialString,
658 hex.EncodeToString(makeSha256([]byte(ctx.canonicalString))),
662 func (ctx *signingCtx) buildSignature() {
663 secret := ctx.credValues.SecretAccessKey
664 date := makeHmac([]byte("AWS4"+secret), []byte(ctx.formattedShortTime))
665 region := makeHmac(date, []byte(ctx.Region))
666 service := makeHmac(region, []byte(ctx.ServiceName))
667 credentials := makeHmac(service, []byte("aws4_request"))
668 signature := makeHmac(credentials, []byte(ctx.stringToSign))
669 ctx.signature = hex.EncodeToString(signature)
672 func (ctx *signingCtx) buildBodyDigest() error {
673 hash := ctx.Request.Header.Get("X-Amz-Content-Sha256")
675 includeSHA256Header := ctx.unsignedPayload ||
676 ctx.ServiceName == "s3" ||
677 ctx.ServiceName == "glacier"
679 s3Presign := ctx.isPresign && ctx.ServiceName == "s3"
681 if ctx.unsignedPayload || s3Presign {
682 hash = "UNSIGNED-PAYLOAD"
683 includeSHA256Header = !s3Presign
684 } else if ctx.Body == nil {
685 hash = emptyStringSHA256
687 if !aws.IsReaderSeekable(ctx.Body) {
688 return fmt.Errorf("cannot use unseekable request body %T, for signed request with body", ctx.Body)
690 hash = hex.EncodeToString(makeSha256Reader(ctx.Body))
693 if includeSHA256Header {
694 ctx.Request.Header.Set("X-Amz-Content-Sha256", hash)
697 ctx.bodyDigest = hash
702 // isRequestSigned returns if the request is currently signed or presigned
703 func (ctx *signingCtx) isRequestSigned() bool {
704 if ctx.isPresign && ctx.Query.Get("X-Amz-Signature") != "" {
707 if ctx.Request.Header.Get("Authorization") != "" {
714 // unsign removes signing flags for both signed and presigned requests.
715 func (ctx *signingCtx) removePresign() {
716 ctx.Query.Del("X-Amz-Algorithm")
717 ctx.Query.Del("X-Amz-Signature")
718 ctx.Query.Del("X-Amz-Security-Token")
719 ctx.Query.Del("X-Amz-Date")
720 ctx.Query.Del("X-Amz-Expires")
721 ctx.Query.Del("X-Amz-Credential")
722 ctx.Query.Del("X-Amz-SignedHeaders")
725 func makeHmac(key []byte, data []byte) []byte {
726 hash := hmac.New(sha256.New, key)
731 func makeSha256(data []byte) []byte {
737 func makeSha256Reader(reader io.ReadSeeker) []byte {
739 start, _ := reader.Seek(0, sdkio.SeekCurrent)
740 defer reader.Seek(start, sdkio.SeekStart)
742 io.Copy(hash, reader)
746 const doubleSpace = " "
748 // stripExcessSpaces will rewrite the passed in slice's string values to not
749 // contain muliple side-by-side spaces.
750 func stripExcessSpaces(vals []string) {
751 var j, k, l, m, spaces int
752 for i, str := range vals {
753 // Trim trailing spaces
754 for j = len(str) - 1; j >= 0 && str[j] == ' '; j-- {
757 // Trim leading spaces
758 for k = 0; k < j && str[k] == ' '; k++ {
762 // Strip multiple spaces.
763 j = strings.Index(str, doubleSpace)
770 for k, m, l = j, j, len(buf); k < l; k++ {
779 // End of multiple spaces.
786 vals[i] = string(buf[:m])