MM-10417 Add local image proxy and enable by default (#9967)
* MM-10417 Add local image proxy and enable by default * Remove unused function * Add dependencies for willnorris/imageproxy * Fixed compilation errors * Lock to the master version of willnorris/imageproxy * Fix atmos/camo proxy when no SiteURL is specified * Re-add default values for deprecated settings * Fix unit tests added by merge * Pass imageproxy to App struct * Remove unneeded locking when creating the image proxy * Remove empty test file
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
e961b4cd0d
Коммит
ba5566d1a0
202
vendor/willnorris.com/go/gifresize/LICENSE
сгенерированный
поставляемый
Обычный файл
202
vendor/willnorris.com/go/gifresize/LICENSE
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
15
vendor/willnorris.com/go/gifresize/README.md
сгенерированный
поставляемый
Обычный файл
15
vendor/willnorris.com/go/gifresize/README.md
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,15 @@
|
||||
# gifresize [](https://godoc.org/willnorris.com/go/gifresize) [](LICENSE)
|
||||
|
||||
gifresize is a simple go package for transforming animated GIFs.
|
||||
|
||||
Import using:
|
||||
|
||||
```go
|
||||
import "willnorris.com/go/gifresize"
|
||||
```
|
||||
|
||||
Then call `gifresize.Process` with the source io.Reader and destination
|
||||
io.Writer as well as the transformation to be applied to each frame in the GIF.
|
||||
See [example/main.go][] for a simple example.
|
||||
|
||||
[example/main.go]: ./example/main.go
|
||||
85
vendor/willnorris.com/go/gifresize/gifresize.go
сгенерированный
поставляемый
Обычный файл
85
vendor/willnorris.com/go/gifresize/gifresize.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,85 @@
|
||||
// Copyright 2013 Daniel Pupius. All rights reserved.
|
||||
// Copyright 2015 Google. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package gifresize resizes animated gifs.
|
||||
//
|
||||
// Frames in an animated gif aren't necessarily the same size, subsequent
|
||||
// frames are overlayed on previous frames. Therefore, resizing the frames
|
||||
// individually may cause problems due to aliasing of transparent pixels. This
|
||||
// package tries to avoid this by building frames from all previous frames and
|
||||
// resizing the frames as RGB.
|
||||
package gifresize // import "willnorris.com/go/gifresize"
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
"image/draw"
|
||||
"image/gif"
|
||||
"io"
|
||||
)
|
||||
|
||||
// TransformFunc is a function that transforms an image.
|
||||
type TransformFunc func(image.Image) image.Image
|
||||
|
||||
// Process the GIF read from r, applying transform to each frame, and writing
|
||||
// the result to w.
|
||||
func Process(w io.Writer, r io.Reader, transform TransformFunc) error {
|
||||
if transform == nil {
|
||||
_, err := io.Copy(w, r)
|
||||
return err
|
||||
}
|
||||
|
||||
// Decode the original gif.
|
||||
im, err := gif.DecodeAll(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create a new RGBA image to hold the incremental frames.
|
||||
firstFrame := im.Image[0].Bounds()
|
||||
b := image.Rect(0, 0, firstFrame.Dx(), firstFrame.Dy())
|
||||
img := image.NewRGBA(b)
|
||||
|
||||
// Resize each frame.
|
||||
for index, frame := range im.Image {
|
||||
bounds := frame.Bounds()
|
||||
previous := img
|
||||
draw.Draw(img, bounds, frame, bounds.Min, draw.Over)
|
||||
im.Image[index] = imageToPaletted(transform(img), frame.Palette)
|
||||
|
||||
switch im.Disposal[index] {
|
||||
case gif.DisposalBackground:
|
||||
// I'm just assuming that the gif package will apply the appropriate
|
||||
// background here, since there doesn't seem to be an easy way to
|
||||
// access the global color table
|
||||
img = image.NewRGBA(b)
|
||||
case gif.DisposalPrevious:
|
||||
img = previous
|
||||
}
|
||||
}
|
||||
|
||||
// Set image.Config to new height and width
|
||||
im.Config.Width = im.Image[0].Bounds().Max.X
|
||||
im.Config.Height = im.Image[0].Bounds().Max.Y
|
||||
|
||||
return gif.EncodeAll(w, im)
|
||||
}
|
||||
|
||||
func imageToPaletted(img image.Image, p color.Palette) *image.Paletted {
|
||||
b := img.Bounds()
|
||||
pm := image.NewPaletted(b, p)
|
||||
draw.FloydSteinberg.Draw(pm, b, img, image.ZP)
|
||||
return pm
|
||||
}
|
||||
1
vendor/willnorris.com/go/gifresize/go.mod
сгенерированный
поставляемый
Обычный файл
1
vendor/willnorris.com/go/gifresize/go.mod
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1 @@
|
||||
module willnorris.com/go/gifresize
|
||||
12
vendor/willnorris.com/go/imageproxy/.gitignore
сгенерированный
поставляемый
Обычный файл
12
vendor/willnorris.com/go/imageproxy/.gitignore
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,12 @@
|
||||
build
|
||||
.goxc.local.json
|
||||
*.test
|
||||
|
||||
vendor/**/.travis.yml
|
||||
vendor/**/.gitignore
|
||||
vendor/**/.goxc.json
|
||||
vendor/**/AUTHORS
|
||||
vendor/**/CONTRIBUTORS
|
||||
vendor/**/Makefile
|
||||
vendor/**/*_ZH.md
|
||||
vendor/**/*.sh
|
||||
16
vendor/willnorris.com/go/imageproxy/.travis.yml
сгенерированный
поставляемый
Обычный файл
16
vendor/willnorris.com/go/imageproxy/.travis.yml
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,16 @@
|
||||
language: go
|
||||
sudo: false
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- go: "1.10.x"
|
||||
script: go test -v ./...
|
||||
- go: "1.11.x"
|
||||
script: go test -v -mod=vendor ./...
|
||||
|
||||
go_import_path: willnorris.com/go/imageproxy
|
||||
|
||||
env:
|
||||
- GO111MODULE=on
|
||||
|
||||
install: true
|
||||
22
vendor/willnorris.com/go/imageproxy/Dockerfile
сгенерированный
поставляемый
Обычный файл
22
vendor/willnorris.com/go/imageproxy/Dockerfile
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,22 @@
|
||||
FROM golang:1.9 as build
|
||||
MAINTAINER Will Norris <will@willnorris.com>
|
||||
|
||||
WORKDIR /go/src/willnorris.com/go/imageproxy
|
||||
ADD . .
|
||||
|
||||
WORKDIR /go/src/willnorris.com/go/imageproxy/cmd/imageproxy
|
||||
RUN go-wrapper download
|
||||
RUN CGO_ENABLED=0 GOOS=linux go-wrapper install
|
||||
|
||||
FROM scratch
|
||||
|
||||
WORKDIR /go/bin
|
||||
|
||||
COPY --from=build /usr/share/zoneinfo /usr/share/zoneinfo
|
||||
COPY --from=build /etc/ssl/certs /etc/ssl/certs
|
||||
COPY --from=build /go/bin/imageproxy .
|
||||
|
||||
CMD ["-addr", "0.0.0.0:8080"]
|
||||
ENTRYPOINT ["/go/bin/imageproxy"]
|
||||
|
||||
EXPOSE 8080
|
||||
178
vendor/willnorris.com/go/imageproxy/LICENSE
сгенерированный
поставляемый
Обычный файл
178
vendor/willnorris.com/go/imageproxy/LICENSE
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,178 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
310
vendor/willnorris.com/go/imageproxy/README.md
сгенерированный
поставляемый
Обычный файл
310
vendor/willnorris.com/go/imageproxy/README.md
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,310 @@
|
||||
# imageproxy
|
||||
|
||||
[](https://godoc.org/willnorris.com/go/imageproxy)
|
||||
[](https://travis-ci.org/willnorris/imageproxy)
|
||||
[](LICENSE)
|
||||
|
||||
imageproxy is a caching image proxy server written in go. It features:
|
||||
|
||||
- basic image adjustments like resizing, cropping, and rotation
|
||||
- access control using allowed hosts list or request signing (HMAC-SHA256)
|
||||
- support for jpeg, png, webp (decode only), tiff, and gif image formats
|
||||
(including animated gifs)
|
||||
- caching in-memory, on disk, or with Amazon S3, Google Cloud Storage, Azure
|
||||
Storage, or Redis
|
||||
- easy deployment, since it's pure go
|
||||
|
||||
Personally, I use it primarily to dynamically resize images hosted on my own
|
||||
site (read more in [this post][]). But you can also enable request signing and
|
||||
use it as an SSL proxy for remote images, similar to [atmos/camo][] but with
|
||||
additional image adjustment options.
|
||||
|
||||
[this post]: https://willnorris.com/2014/01/a-self-hosted-alternative-to-jetpacks-photon-service
|
||||
[atmos/camo]: https://github.com/atmos/camo
|
||||
|
||||
|
||||
## URL Structure ##
|
||||
|
||||
imageproxy URLs are of the form `http://localhost/{options}/{remote_url}`.
|
||||
|
||||
### Options ###
|
||||
|
||||
Options are available for cropping, resizing, rotation, flipping, and digital
|
||||
signatures among a few others. Options for are specified as a comma delimited
|
||||
list of parameters, which can be supplied in any order. Duplicate parameters
|
||||
overwrite previous values.
|
||||
|
||||
See the full list of available options at
|
||||
<https://godoc.org/willnorris.com/go/imageproxy#ParseOptions>.
|
||||
|
||||
### Remote URL ###
|
||||
|
||||
The URL of the original image to load is specified as the remainder of the
|
||||
path, without any encoding. For example,
|
||||
`http://localhost/200/https://willnorris.com/logo.jpg`.
|
||||
|
||||
In order to [optimize caching][], it is recommended that URLs not contain query
|
||||
strings.
|
||||
|
||||
[optimize caching]: http://www.stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring/
|
||||
|
||||
### Examples ###
|
||||
|
||||
The following live examples demonstrate setting different options on [this
|
||||
source image][small-things], which measures 1024 by 678 pixels.
|
||||
|
||||
[small-things]: https://willnorris.com/2013/12/small-things.jpg
|
||||
|
||||
Options | Meaning | Image
|
||||
--------|------------------------------------------|------
|
||||
200x | 200px wide, proportional height | <a href="https://willnorris.com/api/imageproxy/200x/https://willnorris.com/2013/12/small-things.jpg"><img src="https://willnorris.com/api/imageproxy/200x/https://willnorris.com/2013/12/small-things.jpg" alt="200x"></a>
|
||||
x0.15 | 15% original height, proportional width | <a href="https://willnorris.com/api/imageproxy/x0.15/https://willnorris.com/2013/12/small-things.jpg"><img src="https://willnorris.com/api/imageproxy/x0.15/https://willnorris.com/2013/12/small-things.jpg" alt="x0.15"></a>
|
||||
100x150 | 100 by 150 pixels, cropping as needed | <a href="https://willnorris.com/api/imageproxy/100x150/https://willnorris.com/2013/12/small-things.jpg"><img src="https://willnorris.com/api/imageproxy/100x150/https://willnorris.com/2013/12/small-things.jpg" alt="100x150"></a>
|
||||
100 | 100px square, cropping as needed | <a href="https://willnorris.com/api/imageproxy/100/https://willnorris.com/2013/12/small-things.jpg"><img src="https://willnorris.com/api/imageproxy/100/https://willnorris.com/2013/12/small-things.jpg" alt="100"></a>
|
||||
150,fit | scale to fit 150px square, no cropping | <a href="https://willnorris.com/api/imageproxy/150,fit/https://willnorris.com/2013/12/small-things.jpg"><img src="https://willnorris.com/api/imageproxy/150,fit/https://willnorris.com/2013/12/small-things.jpg" alt="150,fit"></a>
|
||||
100,r90 | 100px square, rotated 90 degrees | <a href="https://willnorris.com/api/imageproxy/100,r90/https://willnorris.com/2013/12/small-things.jpg"><img src="https://willnorris.com/api/imageproxy/100,r90/https://willnorris.com/2013/12/small-things.jpg" alt="100,r90"></a>
|
||||
100,fv,fh | 100px square, flipped horizontal and vertical | <a href="https://willnorris.com/api/imageproxy/100,fv,fh/https://willnorris.com/2013/12/small-things.jpg"><img src="https://willnorris.com/api/imageproxy/100,fv,fh/https://willnorris.com/2013/12/small-things.jpg" alt="100,fv,fh"></a>
|
||||
200x,q60 | 200px wide, proportional height, 60% quality | <a href="https://willnorris.com/api/imageproxy/200x,q60/https://willnorris.com/2013/12/small-things.jpg"><img src="https://willnorris.com/api/imageproxy/200x,q60/https://willnorris.com/2013/12/small-things.jpg" alt="200x,q60"></a>
|
||||
200x,png | 200px wide, converted to PNG format | <a href="https://willnorris.com/api/imageproxy/200x,png/https://willnorris.com/2013/12/small-things.jpg"><img src="https://willnorris.com/api/imageproxy/200x,png/https://willnorris.com/2013/12/small-things.jpg" alt="200x,png"></a>
|
||||
cx175,cw400,ch300,100x | crop to 400x300px starting at (175,0), scale to 100px wide | <a href="https://willnorris.com/api/imageproxy/cx175,cw400,ch300,100x/https://willnorris.com/2013/12/small-things.jpg"><img src="https://willnorris.com/api/imageproxy/cx175,cw400,ch300,100x/https://willnorris.com/2013/12/small-things.jpg" alt="cx175,cw400,ch300,100x"></a>
|
||||
|
||||
Transformation also works on animated gifs. Here is [this source
|
||||
image][material-animation] resized to 200px square and rotated 270 degrees:
|
||||
|
||||
[material-animation]: https://willnorris.com/2015/05/material-animations.gif
|
||||
|
||||
<a href="https://willnorris.com/api/imageproxy/200,r270/https://willnorris.com/2015/05/material-animations.gif"><img src="https://willnorris.com/api/imageproxy/200,r270/https://willnorris.com/2015/05/material-animations.gif" alt="200,r270"></a>
|
||||
|
||||
The smart crop feature can best be seen by comparing the following images, with and without smart crop.
|
||||
|
||||
<a href="https://willnorris.com/api/imageproxy/150x300,sc/https://judahnorris.com/images/judah-sheets.jpg"><img src="https://willnorris.com/api/imageproxy/150x300,sc/https://judahnorris.com/images/judah-sheets.jpg" alt="200x400,sc"></a>
|
||||
<a href="https://willnorris.com/api/imageproxy/150x300/https://judahnorris.com/images/judah-sheets.jpg"><img src="https://willnorris.com/api/imageproxy/150x300/https://judahnorris.com/images/judah-sheets.jpg" alt="200x400"></a>
|
||||
|
||||
## Getting Started ##
|
||||
|
||||
Install the package using:
|
||||
|
||||
go get willnorris.com/go/imageproxy/cmd/imageproxy
|
||||
|
||||
Once installed, ensure `$GOPATH/bin` is in your `$PATH`, then run the proxy
|
||||
using:
|
||||
|
||||
imageproxy
|
||||
|
||||
This will start the proxy on port 8080, without any caching and with no allowed
|
||||
host list (meaning any remote URL can be proxied). Test this by navigating to
|
||||
<http://localhost:8080/500/https://octodex.github.com/images/codercat.jpg> and
|
||||
you should see a 500px square coder octocat.
|
||||
|
||||
### Cache ###
|
||||
|
||||
By default, the imageproxy command does not cache responses, but caching can be
|
||||
enabled using the `-cache` flag. It supports the following values:
|
||||
|
||||
- `memory` - uses an in-memory LRU cache. By default, this is limited to
|
||||
100mb. To customize the size of the cache or the max age for cached items,
|
||||
use the format `memory:size:age` where size is measured in mb and age is a
|
||||
duration. For example, `memory:200:4h` will create a 200mb cache that will
|
||||
cache items no longer than 4 hours.
|
||||
- directory on local disk (e.g. `/tmp/imageproxy`) - will cache images
|
||||
on disk
|
||||
- s3 URL (e.g. `s3://region/bucket-name/optional-path-prefix`) - will cache
|
||||
images on Amazon S3. This requires either an IAM role and instance profile
|
||||
with access to your your bucket or `AWS_ACCESS_KEY_ID` and `AWS_SECRET_KEY`
|
||||
environmental variables be set. (Additional methods of loading credentials
|
||||
are documented in the [aws-sdk-go session
|
||||
package](https://docs.aws.amazon.com/sdk-for-go/api/aws/session/)).
|
||||
- gcs URL (e.g. `gcs://bucket-name/optional-path-prefix`) - will cache images
|
||||
on Google Cloud Storage. Authentication is documented in Google's
|
||||
[Application Default Credentials
|
||||
docs](https://cloud.google.com/docs/authentication/production#providing_credentials_to_your_application).
|
||||
- azure URL (e.g. `azure://container-name/`) - will cache images on
|
||||
Azure Storage. This requires `AZURESTORAGE_ACCOUNT_NAME` and
|
||||
`AZURESTORAGE_ACCESS_KEY` environment variables to bet set.
|
||||
- redis URL (e.g. `redis://hostname/`) - will cache images on
|
||||
the specified redis host. The full URL syntax is defined by the [redis URI
|
||||
registration](https://www.iana.org/assignments/uri-schemes/prov/redis).
|
||||
Rather than specify password in the URI, use the `REDIS_PASSWORD`
|
||||
environment variable.
|
||||
|
||||
For example, to cache files on disk in the `/tmp/imageproxy` directory:
|
||||
|
||||
imageproxy -cache /tmp/imageproxy
|
||||
|
||||
Reload the [codercat URL][], and then inspect the contents of
|
||||
`/tmp/imageproxy`. Within the subdirectories, there should be two files, one
|
||||
for the original full-size codercat image, and one for the resized 500px
|
||||
version.
|
||||
|
||||
[codercat URL]: http://localhost:8080/500/https://octodex.github.com/images/codercat.jpg
|
||||
|
||||
If the `-cache` flag is specified multiple times, multiple caches will be
|
||||
created in a [tiered fashion][]. Typically this is used to put a smaller and
|
||||
faster in-memory cache in front of a larger but slower on-disk cache. For
|
||||
example, the following will first check an in-memory cache for an image,
|
||||
followed by a gcs bucket:
|
||||
|
||||
imageproxy -cache memory -cache gcs://my-bucket/
|
||||
|
||||
[tiered fashion]: https://godoc.org/github.com/die-net/lrucache/twotier
|
||||
|
||||
### Allowed Referrer List ###
|
||||
|
||||
You can limit images to only be accessible for certain hosts in the HTTP
|
||||
referrer header, which can help prevent others from hotlinking to images. It can
|
||||
be enabled by running:
|
||||
|
||||
imageproxy -referrers example.com
|
||||
|
||||
|
||||
Reload the [codercat URL][], and you should now get an error message. You can
|
||||
specify multiple hosts as a comma separated list, or prefix a host value with
|
||||
`*.` to allow all sub-domains as well.
|
||||
|
||||
### Allowed Hosts List ###
|
||||
|
||||
You can limit the remote hosts that the proxy will fetch images from using the
|
||||
`remoteHosts` flag. This is useful, for example, for locking the proxy down to
|
||||
your own hosts to prevent others from abusing it. Of course if you want to
|
||||
support fetching from any host, leave off the remoteHosts flag. Try it out by
|
||||
running:
|
||||
|
||||
imageproxy -remoteHosts example.com
|
||||
|
||||
Reload the [codercat URL][], and you should now get an error message. You can
|
||||
specify multiple hosts as a comma separated list, or prefix a host value with
|
||||
`*.` to allow all sub-domains as well.
|
||||
|
||||
### Allowed Content-Type List ###
|
||||
|
||||
You can limit what content types can be proxied by using the `contentTypes`
|
||||
flag. By default, this is set to `image/*`, meaning that imageproxy will
|
||||
process any image types. You can specify multiple content types as a comma
|
||||
separated list, and suffix values with `*` to perform a wildcard match. Set the
|
||||
flag to an empty string to proxy all requests, regardless of content type.
|
||||
|
||||
### Signed Requests ###
|
||||
|
||||
Instead of an allowed host list, you can require that requests be signed. This
|
||||
is useful in preventing abuse when you don't have just a static list of hosts
|
||||
you want to allow. Signatures are generated using HMAC-SHA256 against the
|
||||
remote URL, and url-safe base64 encoding the result:
|
||||
|
||||
base64urlencode(hmac.New(sha256, <key>).digest(<remote_url>))
|
||||
|
||||
The HMAC key is specified using the `signatureKey` flag. If this flag
|
||||
begins with an "@", the remainder of the value is interpreted as a file on disk
|
||||
which contains the HMAC key.
|
||||
|
||||
Try it out by running:
|
||||
|
||||
imageproxy -signatureKey "secret key"
|
||||
|
||||
Reload the [codercat URL][], and you should see an error message. Now load a
|
||||
[signed codercat URL][] and verify that it loads properly.
|
||||
|
||||
[signed codercat URL]: http://localhost:8080/500,sXyMwWKIC5JPCtlYOQ2f4yMBTqpjtUsfI67Sp7huXIYY=/https://octodex.github.com/images/codercat.jpg
|
||||
|
||||
Some simple code samples for generating signatures in various languages can be
|
||||
found in [URL Signing](https://github.com/willnorris/imageproxy/wiki/URL-signing).
|
||||
|
||||
If both a whiltelist and signatureKey are specified, requests can match either.
|
||||
In other words, requests that match one of the allowed hosts don't necessarily
|
||||
need to be signed, though they can be.
|
||||
|
||||
### Default Base URL ###
|
||||
|
||||
Typically, remote images to be proxied are specified as absolute URLs.
|
||||
However, if you commonly proxy images from a single source, you can provide a
|
||||
base URL and then specify remote images relative to that base. Try it out by
|
||||
running:
|
||||
|
||||
imageproxy -baseURL https://octodex.github.com/
|
||||
|
||||
Then load the codercat image, specified as a URL relative to that base:
|
||||
<http://localhost:8080/500/images/codercat.jpg>. Note that this is not an
|
||||
effective method to mask the true source of the images being proxied; it is
|
||||
trivial to discover the base URL being used. Even when a base URL is
|
||||
specified, you can always provide the absolute URL of the image to be proxied.
|
||||
|
||||
### Scaling beyond original size ###
|
||||
|
||||
By default, the imageproxy won't scale images beyond their original size.
|
||||
However, you can use the `scaleUp` command-line flag to allow this to happen:
|
||||
|
||||
imageproxy -scaleUp true
|
||||
|
||||
### WebP and TIFF support ###
|
||||
|
||||
Imageproxy can proxy remote webp images, but they will be served in either jpeg
|
||||
or png format (this is because the golang webp library only supports webp
|
||||
decoding) if any transformation is requested. If no format is specified,
|
||||
imageproxy will use jpeg by default. If no transformation is requested (for
|
||||
example, if you are just using imageproxy as an SSL proxy) then the original
|
||||
webp image will be served as-is without any format conversion.
|
||||
|
||||
Because so few browsers support tiff images, they will be converted to jpeg by
|
||||
default if any transformation is requested. To force encoding as tiff, pass the
|
||||
"tiff" option. Like webp, tiff images will be served as-is without any format
|
||||
conversion if no transformation is requested.
|
||||
|
||||
|
||||
Run `imageproxy -help` for a complete list of flags the command accepts. If
|
||||
you want to use a different caching implementation, it's probably easiest to
|
||||
just make a copy of `cmd/imageproxy/main.go` and customize it to fit your
|
||||
needs... it's a very simple command.
|
||||
|
||||
## Deploying ##
|
||||
|
||||
In most cases, you can follow the normal procedure for building a deploying any
|
||||
go application. For example, I build it directly on my production debian server
|
||||
using:
|
||||
|
||||
- `go build willnorris.com/go/imageproxy/cmd/imageproxy`
|
||||
- copy resulting binary to `/usr/local/bin`
|
||||
- copy [`etc/imageproxy.service`](etc/imageproxy.service) to
|
||||
`/lib/systemd/system` and enable using `systemctl`.
|
||||
|
||||
Instructions have been contributed below for running on other platforms, but I
|
||||
don't have much experience with them personally.
|
||||
|
||||
### Heroku ###
|
||||
|
||||
It's easy to vendorize the dependencies with `Godep` and deploy to Heroku. Take
|
||||
a look at [this GitHub repo](https://github.com/oreillymedia/prototype-imageproxy)
|
||||
|
||||
### Docker ###
|
||||
|
||||
A docker image is available at [`willnorris/imageproxy`](https://registry.hub.docker.com/u/willnorris/imageproxy/dockerfile/).
|
||||
|
||||
You can run it by
|
||||
```
|
||||
docker run -p 8080:8080 willnorris/imageproxy -addr 0.0.0.0:8080
|
||||
```
|
||||
|
||||
Or in your Dockerfile:
|
||||
|
||||
```
|
||||
ENTRYPOINT ["/go/bin/imageproxy", "-addr 0.0.0.0:8080"]
|
||||
```
|
||||
|
||||
### nginx ###
|
||||
|
||||
You can use follow config to prevent URL overwritting:
|
||||
|
||||
```
|
||||
location ~ ^/api/imageproxy/ {
|
||||
# pattern match to capture the original URL to prevent URL
|
||||
# canonicalization, which would strip double slashes
|
||||
if ($request_uri ~ "/api/imageproxy/(.+)") {
|
||||
set $path $1;
|
||||
rewrite .* /$path break;
|
||||
}
|
||||
proxy_pass http://localhost:8080;
|
||||
}
|
||||
```
|
||||
|
||||
## License ##
|
||||
|
||||
imageproxy is copyright Google, but is not an official Google product. It is
|
||||
available under the [Apache 2.0 License](./LICENSE).
|
||||
37
vendor/willnorris.com/go/imageproxy/cache.go
сгенерированный
поставляемый
Обычный файл
37
vendor/willnorris.com/go/imageproxy/cache.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,37 @@
|
||||
// Copyright 2013 Google Inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package imageproxy
|
||||
|
||||
// The Cache interface defines a cache for storing arbitrary data. The
|
||||
// interface is designed to align with httpcache.Cache.
|
||||
type Cache interface {
|
||||
// Get retrieves the cached data for the provided key.
|
||||
Get(key string) (data []byte, ok bool)
|
||||
|
||||
// Set caches the provided data.
|
||||
Set(key string, data []byte)
|
||||
|
||||
// Delete deletes the cached data at the specified key.
|
||||
Delete(key string)
|
||||
}
|
||||
|
||||
// NopCache provides a no-op cache implementation that doesn't actually cache anything.
|
||||
var NopCache = new(nopCache)
|
||||
|
||||
type nopCache struct{}
|
||||
|
||||
func (c nopCache) Get(string) ([]byte, bool) { return nil, false }
|
||||
func (c nopCache) Set(string, []byte) {}
|
||||
func (c nopCache) Delete(string) {}
|
||||
378
vendor/willnorris.com/go/imageproxy/data.go
сгенерированный
поставляемый
Обычный файл
378
vendor/willnorris.com/go/imageproxy/data.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,378 @@
|
||||
// Copyright 2013 Google Inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package imageproxy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
optFit = "fit"
|
||||
optFlipVertical = "fv"
|
||||
optFlipHorizontal = "fh"
|
||||
optFormatJPEG = "jpeg"
|
||||
optFormatPNG = "png"
|
||||
optFormatTIFF = "tiff"
|
||||
optRotatePrefix = "r"
|
||||
optQualityPrefix = "q"
|
||||
optSignaturePrefix = "s"
|
||||
optSizeDelimiter = "x"
|
||||
optScaleUp = "scaleUp"
|
||||
optCropX = "cx"
|
||||
optCropY = "cy"
|
||||
optCropWidth = "cw"
|
||||
optCropHeight = "ch"
|
||||
optSmartCrop = "sc"
|
||||
)
|
||||
|
||||
// URLError reports a malformed URL error.
|
||||
type URLError struct {
|
||||
Message string
|
||||
URL *url.URL
|
||||
}
|
||||
|
||||
func (e URLError) Error() string {
|
||||
return fmt.Sprintf("malformed URL %q: %s", e.URL, e.Message)
|
||||
}
|
||||
|
||||
// Options specifies transformations to be performed on the requested image.
|
||||
type Options struct {
|
||||
// See ParseOptions for interpretation of Width and Height values
|
||||
Width float64
|
||||
Height float64
|
||||
|
||||
// If true, resize the image to fit in the specified dimensions. Image
|
||||
// will not be cropped, and aspect ratio will be maintained.
|
||||
Fit bool
|
||||
|
||||
// Rotate image the specified degrees counter-clockwise. Valid values
|
||||
// are 90, 180, 270.
|
||||
Rotate int
|
||||
|
||||
FlipVertical bool
|
||||
FlipHorizontal bool
|
||||
|
||||
// Quality of output image
|
||||
Quality int
|
||||
|
||||
// HMAC Signature for signed requests.
|
||||
Signature string
|
||||
|
||||
// Allow image to scale beyond its original dimensions. This value
|
||||
// will always be overwritten by the value of Proxy.ScaleUp.
|
||||
ScaleUp bool
|
||||
|
||||
// Desired image format. Valid values are "jpeg", "png", "tiff".
|
||||
Format string
|
||||
|
||||
// Crop rectangle params
|
||||
CropX float64
|
||||
CropY float64
|
||||
CropWidth float64
|
||||
CropHeight float64
|
||||
|
||||
// Automatically find good crop points based on image content.
|
||||
SmartCrop bool
|
||||
}
|
||||
|
||||
func (o Options) String() string {
|
||||
opts := []string{fmt.Sprintf("%v%s%v", o.Width, optSizeDelimiter, o.Height)}
|
||||
if o.Fit {
|
||||
opts = append(opts, optFit)
|
||||
}
|
||||
if o.Rotate != 0 {
|
||||
opts = append(opts, fmt.Sprintf("%s%d", string(optRotatePrefix), o.Rotate))
|
||||
}
|
||||
if o.FlipVertical {
|
||||
opts = append(opts, optFlipVertical)
|
||||
}
|
||||
if o.FlipHorizontal {
|
||||
opts = append(opts, optFlipHorizontal)
|
||||
}
|
||||
if o.Quality != 0 {
|
||||
opts = append(opts, fmt.Sprintf("%s%d", string(optQualityPrefix), o.Quality))
|
||||
}
|
||||
if o.Signature != "" {
|
||||
opts = append(opts, fmt.Sprintf("%s%s", string(optSignaturePrefix), o.Signature))
|
||||
}
|
||||
if o.ScaleUp {
|
||||
opts = append(opts, optScaleUp)
|
||||
}
|
||||
if o.Format != "" {
|
||||
opts = append(opts, o.Format)
|
||||
}
|
||||
if o.CropX != 0 {
|
||||
opts = append(opts, fmt.Sprintf("%s%v", string(optCropX), o.CropX))
|
||||
}
|
||||
if o.CropY != 0 {
|
||||
opts = append(opts, fmt.Sprintf("%s%v", string(optCropY), o.CropY))
|
||||
}
|
||||
if o.CropWidth != 0 {
|
||||
opts = append(opts, fmt.Sprintf("%s%v", string(optCropWidth), o.CropWidth))
|
||||
}
|
||||
if o.CropHeight != 0 {
|
||||
opts = append(opts, fmt.Sprintf("%s%v", string(optCropHeight), o.CropHeight))
|
||||
}
|
||||
if o.SmartCrop {
|
||||
opts = append(opts, optSmartCrop)
|
||||
}
|
||||
return strings.Join(opts, ",")
|
||||
}
|
||||
|
||||
// transform returns whether o includes transformation options. Some fields
|
||||
// are not transform related at all (like Signature), and others only apply in
|
||||
// the presence of other fields (like Fit). A non-empty Format value is
|
||||
// assumed to involve a transformation.
|
||||
func (o Options) transform() bool {
|
||||
return o.Width != 0 || o.Height != 0 || o.Rotate != 0 || o.FlipHorizontal || o.FlipVertical || o.Quality != 0 || o.Format != "" || o.CropX != 0 || o.CropY != 0 || o.CropWidth != 0 || o.CropHeight != 0
|
||||
}
|
||||
|
||||
// ParseOptions parses str as a list of comma separated transformation options.
|
||||
// The options can be specified in in order, with duplicate options overwriting
|
||||
// previous values.
|
||||
//
|
||||
// Rectangle Crop
|
||||
//
|
||||
// There are four options controlling rectangle crop:
|
||||
//
|
||||
// cx{x} - X coordinate of top left rectangle corner (default: 0)
|
||||
// cy{y} - Y coordinate of top left rectangle corner (default: 0)
|
||||
// cw{width} - rectangle width (default: image width)
|
||||
// ch{height} - rectangle height (default: image height)
|
||||
//
|
||||
// For all options, integer values are interpreted as exact pixel values and
|
||||
// floats between 0 and 1 are interpreted as percentages of the original image
|
||||
// size. Negative values for cx and cy are measured from the right and bottom
|
||||
// edges of the image, respectively.
|
||||
//
|
||||
// If the crop width or height exceed the width or height of the image, the
|
||||
// crop width or height will be adjusted, preserving the specified cx and cy
|
||||
// values. Rectangular crop is applied before any other transformations.
|
||||
//
|
||||
// Smart Crop
|
||||
//
|
||||
// The "sc" option will perform a content-aware smart crop to fit the
|
||||
// requested image width and height dimensions (see Size and Cropping below).
|
||||
// The smart crop option will override any requested rectangular crop.
|
||||
//
|
||||
// Size and Cropping
|
||||
//
|
||||
// The size option takes the general form "{width}x{height}", where width and
|
||||
// height are numbers. Integer values greater than 1 are interpreted as exact
|
||||
// pixel values. Floats between 0 and 1 are interpreted as percentages of the
|
||||
// original image size. If either value is omitted or set to 0, it will be
|
||||
// automatically set to preserve the aspect ratio based on the other dimension.
|
||||
// If a single number is provided (with no "x" separator), it will be used for
|
||||
// both height and width.
|
||||
//
|
||||
// Depending on the size options specified, an image may be cropped to fit the
|
||||
// requested size. In all cases, the original aspect ratio of the image will be
|
||||
// preserved; imageproxy will never stretch the original image.
|
||||
//
|
||||
// When no explicit crop mode is specified, the following rules are followed:
|
||||
//
|
||||
// - If both width and height values are specified, the image will be scaled to
|
||||
// fill the space, cropping if necessary to fit the exact dimension.
|
||||
//
|
||||
// - If only one of the width or height values is specified, the image will be
|
||||
// resized to fit the specified dimension, scaling the other dimension as
|
||||
// needed to maintain the aspect ratio.
|
||||
//
|
||||
// If the "fit" option is specified together with a width and height value, the
|
||||
// image will be resized to fit within a containing box of the specified size.
|
||||
// As always, the original aspect ratio will be preserved. Specifying the "fit"
|
||||
// option with only one of either width or height does the same thing as if
|
||||
// "fit" had not been specified.
|
||||
//
|
||||
// Rotation and Flips
|
||||
//
|
||||
// The "r{degrees}" option will rotate the image the specified number of
|
||||
// degrees, counter-clockwise. Valid degrees values are 90, 180, and 270.
|
||||
//
|
||||
// The "fv" option will flip the image vertically. The "fh" option will flip
|
||||
// the image horizontally. Images are flipped after being rotated.
|
||||
//
|
||||
// Quality
|
||||
//
|
||||
// The "q{qualityPercentage}" option can be used to specify the quality of the
|
||||
// output file (JPEG only). If not specified, the default value of "95" is used.
|
||||
//
|
||||
// Format
|
||||
//
|
||||
// The "jpeg", "png", and "tiff" options can be used to specify the desired
|
||||
// image format of the proxied image.
|
||||
//
|
||||
// Signature
|
||||
//
|
||||
// The "s{signature}" option specifies an optional base64 encoded HMAC used to
|
||||
// sign the remote URL in the request. The HMAC key used to verify signatures is
|
||||
// provided to the imageproxy server on startup.
|
||||
//
|
||||
// See https://github.com/willnorris/imageproxy/wiki/URL-signing
|
||||
// for examples of generating signatures.
|
||||
//
|
||||
// Examples
|
||||
//
|
||||
// 0x0 - no resizing
|
||||
// 200x - 200 pixels wide, proportional height
|
||||
// x0.15 - 15% original height, proportional width
|
||||
// 100x150 - 100 by 150 pixels, cropping as needed
|
||||
// 100 - 100 pixels square, cropping as needed
|
||||
// 150,fit - scale to fit 150 pixels square, no cropping
|
||||
// 100,r90 - 100 pixels square, rotated 90 degrees
|
||||
// 100,fv,fh - 100 pixels square, flipped horizontal and vertical
|
||||
// 200x,q60 - 200 pixels wide, proportional height, 60% quality
|
||||
// 200x,png - 200 pixels wide, converted to PNG format
|
||||
// cw100,ch100 - crop image to 100px square, starting at (0,0)
|
||||
// cx10,cy20,cw100,ch200 - crop image starting at (10,20) is 100px wide and 200px tall
|
||||
func ParseOptions(str string) Options {
|
||||
var options Options
|
||||
|
||||
for _, opt := range strings.Split(str, ",") {
|
||||
switch {
|
||||
case len(opt) == 0:
|
||||
break
|
||||
case opt == optFit:
|
||||
options.Fit = true
|
||||
case opt == optFlipVertical:
|
||||
options.FlipVertical = true
|
||||
case opt == optFlipHorizontal:
|
||||
options.FlipHorizontal = true
|
||||
case opt == optScaleUp: // this option is intentionally not documented above
|
||||
options.ScaleUp = true
|
||||
case opt == optFormatJPEG, opt == optFormatPNG, opt == optFormatTIFF:
|
||||
options.Format = opt
|
||||
case opt == optSmartCrop:
|
||||
options.SmartCrop = true
|
||||
case strings.HasPrefix(opt, optRotatePrefix):
|
||||
value := strings.TrimPrefix(opt, optRotatePrefix)
|
||||
options.Rotate, _ = strconv.Atoi(value)
|
||||
case strings.HasPrefix(opt, optQualityPrefix):
|
||||
value := strings.TrimPrefix(opt, optQualityPrefix)
|
||||
options.Quality, _ = strconv.Atoi(value)
|
||||
case strings.HasPrefix(opt, optSignaturePrefix):
|
||||
options.Signature = strings.TrimPrefix(opt, optSignaturePrefix)
|
||||
case strings.HasPrefix(opt, optCropX):
|
||||
value := strings.TrimPrefix(opt, optCropX)
|
||||
options.CropX, _ = strconv.ParseFloat(value, 64)
|
||||
case strings.HasPrefix(opt, optCropY):
|
||||
value := strings.TrimPrefix(opt, optCropY)
|
||||
options.CropY, _ = strconv.ParseFloat(value, 64)
|
||||
case strings.HasPrefix(opt, optCropWidth):
|
||||
value := strings.TrimPrefix(opt, optCropWidth)
|
||||
options.CropWidth, _ = strconv.ParseFloat(value, 64)
|
||||
case strings.HasPrefix(opt, optCropHeight):
|
||||
value := strings.TrimPrefix(opt, optCropHeight)
|
||||
options.CropHeight, _ = strconv.ParseFloat(value, 64)
|
||||
case strings.Contains(opt, optSizeDelimiter):
|
||||
size := strings.SplitN(opt, optSizeDelimiter, 2)
|
||||
if w := size[0]; w != "" {
|
||||
options.Width, _ = strconv.ParseFloat(w, 64)
|
||||
}
|
||||
if h := size[1]; h != "" {
|
||||
options.Height, _ = strconv.ParseFloat(h, 64)
|
||||
}
|
||||
default:
|
||||
if size, err := strconv.ParseFloat(opt, 64); err == nil {
|
||||
options.Width = size
|
||||
options.Height = size
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
// Request is an imageproxy request which includes a remote URL of an image to
|
||||
// proxy, and an optional set of transformations to perform.
|
||||
type Request struct {
|
||||
URL *url.URL // URL of the image to proxy
|
||||
Options Options // Image transformation to perform
|
||||
Original *http.Request // The original HTTP request
|
||||
}
|
||||
|
||||
// String returns the request URL as a string, with r.Options encoded in the
|
||||
// URL fragment.
|
||||
func (r Request) String() string {
|
||||
u := *r.URL
|
||||
u.Fragment = r.Options.String()
|
||||
return u.String()
|
||||
}
|
||||
|
||||
// NewRequest parses an http.Request into an imageproxy Request. Options and
|
||||
// the remote image URL are specified in the request path, formatted as:
|
||||
// /{options}/{remote_url}. Options may be omitted, so a request path may
|
||||
// simply contain /{remote_url}. The remote URL must be an absolute "http" or
|
||||
// "https" URL, should not be URL encoded, and may contain a query string.
|
||||
//
|
||||
// Assuming an imageproxy server running on localhost, the following are all
|
||||
// valid imageproxy requests:
|
||||
//
|
||||
// http://localhost/100x200/http://example.com/image.jpg
|
||||
// http://localhost/100x200,r90/http://example.com/image.jpg?foo=bar
|
||||
// http://localhost//http://example.com/image.jpg
|
||||
// http://localhost/http://example.com/image.jpg
|
||||
func NewRequest(r *http.Request, baseURL *url.URL) (*Request, error) {
|
||||
var err error
|
||||
req := &Request{Original: r}
|
||||
|
||||
path := r.URL.EscapedPath()[1:] // strip leading slash
|
||||
req.URL, err = parseURL(path)
|
||||
if err != nil || !req.URL.IsAbs() {
|
||||
// first segment should be options
|
||||
parts := strings.SplitN(path, "/", 2)
|
||||
if len(parts) != 2 {
|
||||
return nil, URLError{"too few path segments", r.URL}
|
||||
}
|
||||
|
||||
var err error
|
||||
req.URL, err = parseURL(parts[1])
|
||||
if err != nil {
|
||||
return nil, URLError{fmt.Sprintf("unable to parse remote URL: %v", err), r.URL}
|
||||
}
|
||||
|
||||
req.Options = ParseOptions(parts[0])
|
||||
}
|
||||
|
||||
if baseURL != nil {
|
||||
req.URL = baseURL.ResolveReference(req.URL)
|
||||
}
|
||||
|
||||
if !req.URL.IsAbs() {
|
||||
return nil, URLError{"must provide absolute remote URL", r.URL}
|
||||
}
|
||||
|
||||
if req.URL.Scheme != "http" && req.URL.Scheme != "https" {
|
||||
return nil, URLError{"remote URL must have http or https scheme", r.URL}
|
||||
}
|
||||
|
||||
// query string is always part of the remote URL
|
||||
req.URL.RawQuery = r.URL.RawQuery
|
||||
return req, nil
|
||||
}
|
||||
|
||||
var reCleanedURL = regexp.MustCompile(`^(https?):/+([^/])`)
|
||||
|
||||
// parseURL parses s as a URL, handling URLs that have been munged by
|
||||
// path.Clean or a webserver that collapses multiple slashes.
|
||||
func parseURL(s string) (*url.URL, error) {
|
||||
s = reCleanedURL.ReplaceAllString(s, "$1://$2")
|
||||
return url.Parse(s)
|
||||
}
|
||||
46
vendor/willnorris.com/go/imageproxy/go.mod
сгенерированный
поставляемый
Обычный файл
46
vendor/willnorris.com/go/imageproxy/go.mod
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,46 @@
|
||||
module willnorris.com/go/imageproxy
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.0.0-20180131234750-2de512d2700d
|
||||
github.com/Azure/azure-sdk-for-go v12.3.0-beta+incompatible // indirect
|
||||
github.com/Azure/go-autorest v9.9.0+incompatible // indirect
|
||||
github.com/PaulARoy/azurestoragecache v0.0.0-20170906084534-3c249a3ba788
|
||||
github.com/aws/aws-sdk-go v1.15.35
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dgrijalva/jwt-go v3.1.0+incompatible // indirect
|
||||
github.com/die-net/lrucache v0.0.0-20171111232917-04b9315ab7a6
|
||||
github.com/disintegration/imaging v1.3.0
|
||||
github.com/dnaeon/go-vcr v0.0.0-20180814043457-aafff18a5cc2 // indirect
|
||||
github.com/garyburd/redigo v1.5.0
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect
|
||||
github.com/golang/protobuf v1.0.0 // indirect
|
||||
github.com/google/btree v0.0.0-20180124185431-e89373fe6b4a // indirect
|
||||
github.com/google/go-cmp v0.2.0 // indirect
|
||||
github.com/googleapis/gax-go v2.0.0+incompatible // indirect
|
||||
github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c // indirect
|
||||
github.com/gregjones/httpcache v0.0.0-20171119193500-2bcd89a1743f
|
||||
github.com/jtolds/gls v4.2.1+incompatible // indirect
|
||||
github.com/kr/pretty v0.1.0 // indirect
|
||||
github.com/marstr/guid v0.0.0-20170427235115-8bdf7d1a087c // indirect
|
||||
github.com/muesli/smartcrop v0.0.0-20171215203440-9032446b30f6
|
||||
github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5 // indirect
|
||||
github.com/peterbourgon/diskv v0.0.0-20171120014656-2973218375c3
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rwcarlsen/goexif v0.0.0-20141222211634-41dad3aa0833
|
||||
github.com/satori/go.uuid v0.0.0-20180103174451-36e9d2ebbde5 // indirect
|
||||
github.com/smartystreets/assertions v0.0.0-20180820201707-7c9eb446e3cf // indirect
|
||||
github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a // indirect
|
||||
github.com/stretchr/testify v1.2.2 // indirect
|
||||
golang.org/x/image v0.0.0-20171214225156-12117c17ca67
|
||||
golang.org/x/net v0.0.0-20180201232540-b417086c80e9 // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20180126164932-a032972e2806 // indirect
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f // indirect
|
||||
golang.org/x/text v0.0.0-20171227012246-e19ae1496984 // indirect
|
||||
google.golang.org/api v0.0.0-20180202000329-f7618f4b41ca // indirect
|
||||
google.golang.org/appengine v0.0.0-20171212223047-5bee14b453b4 // indirect
|
||||
google.golang.org/genproto v0.0.0-20180125080656-4eb30f4778ee // indirect
|
||||
google.golang.org/grpc v0.0.0-20180201193814-f9628db66d14 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
|
||||
gopkg.in/yaml.v2 v2.2.1 // indirect
|
||||
willnorris.com/go/gifresize v1.0.0
|
||||
)
|
||||
92
vendor/willnorris.com/go/imageproxy/go.sum
сгенерированный
поставляемый
Обычный файл
92
vendor/willnorris.com/go/imageproxy/go.sum
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,92 @@
|
||||
cloud.google.com/go v0.0.0-20180131234750-2de512d2700d h1:Wj6Dk68uwFxqLKT1lV03UFLcfh5+HTLs+YCGlUc06M8=
|
||||
cloud.google.com/go v0.0.0-20180131234750-2de512d2700d/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/Azure/azure-sdk-for-go v12.3.0-beta+incompatible h1:rrdWQEPInXBEqI10Nfu7FCp7BDdLSLWvkHwDWiXOEjw=
|
||||
github.com/Azure/azure-sdk-for-go v12.3.0-beta+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||
github.com/Azure/go-autorest v9.9.0+incompatible h1:U3u8yuM3dZPg6vjJ/Hl2hQlaP+WNOeSHY6Fk4g1e6Ps=
|
||||
github.com/Azure/go-autorest v9.9.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
||||
github.com/PaulARoy/azurestoragecache v0.0.0-20170906084534-3c249a3ba788 h1:OxWBmk9BZqWOHVs+hrElt/BiexDGcStcsADt0f4cUx8=
|
||||
github.com/PaulARoy/azurestoragecache v0.0.0-20170906084534-3c249a3ba788/go.mod h1:lY1dZd8HBzJ10eqKERHn3CU59tfhzcAVb2c0ZhIWSOk=
|
||||
github.com/aws/aws-sdk-go v1.15.35 h1:roOTR0eOeCgYwrz3NXmrneLFBYdnK8epLedaRMm1kQg=
|
||||
github.com/aws/aws-sdk-go v1.15.35/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgrijalva/jwt-go v3.1.0+incompatible h1:FFziAwDQQ2dz1XClWMkwvukur3evtZx7x/wMHKM1i20=
|
||||
github.com/dgrijalva/jwt-go v3.1.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/die-net/lrucache v0.0.0-20171111232917-04b9315ab7a6 h1:UhONZXj9L+pdHs+qUEPMsQkG3BdhMHSCQk8KXRm83ho=
|
||||
github.com/die-net/lrucache v0.0.0-20171111232917-04b9315ab7a6/go.mod h1:ew0MSjCVDdtGMjF3kzLK9hwdgF5mOE8SbYVF3Rc7mkU=
|
||||
github.com/disintegration/imaging v1.3.0 h1:AihaBC+K3RHP7JL8scqq2GgNQhVzMcCYh7gJbAjAvpo=
|
||||
github.com/disintegration/imaging v1.3.0/go.mod h1:9B/deIUIrliYkyMTuXJd6OUFLcrZ2tf+3Qlwnaf/CjU=
|
||||
github.com/dnaeon/go-vcr v0.0.0-20180814043457-aafff18a5cc2 h1:G9/PqfhOrt8JXnw0DGTfVoOkKHDhOlEZqhE/cu+NvQM=
|
||||
github.com/dnaeon/go-vcr v0.0.0-20180814043457-aafff18a5cc2/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
|
||||
github.com/garyburd/redigo v1.5.0 h1:OcZhiwwjKtBe7TO4TlXpj/1E3I2RVg1uLxwMT4VFF5w=
|
||||
github.com/garyburd/redigo v1.5.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
|
||||
github.com/go-ini/ini v1.25.4 h1:Mujh4R/dH6YL8bxuISne3xX2+qcQ9p0IxKAP6ExWoUo=
|
||||
github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/protobuf v1.0.0 h1:lsek0oXi8iFE9L+EXARyHIjU5rlWIhhTkjDz3vHhWWQ=
|
||||
github.com/golang/protobuf v1.0.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/google/btree v0.0.0-20180124185431-e89373fe6b4a h1:ZJu5NB1Bk5ms4vw0Xu4i+jD32SE9jQXyfnOvwhHqlT0=
|
||||
github.com/google/btree v0.0.0-20180124185431-e89373fe6b4a/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/googleapis/gax-go v2.0.0+incompatible h1:j0GKcs05QVmm7yesiZq2+9cxHkNK9YM6zKx4D2qucQU=
|
||||
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c h1:16eHWuMGvCjSfgRJKqIzapE78onvvTbdi1rMkU00lZw=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gregjones/httpcache v0.0.0-20171119193500-2bcd89a1743f h1:kOkUP6rcVVqC+KlKKENKtgfFfJyDySYhqL9srXooghY=
|
||||
github.com/gregjones/httpcache v0.0.0-20171119193500-2bcd89a1743f/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8 h1:12VvqtR6Aowv3l/EQUlocDHW2Cp4G9WJVH7uyH8QFJE=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
github.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpRVWLVmUEE=
|
||||
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/marstr/guid v0.0.0-20170427235115-8bdf7d1a087c h1:N7uWGS2fTwH/4BwxbHiJZNAFTSJ5yPU0emHsQWvkxEY=
|
||||
github.com/marstr/guid v0.0.0-20170427235115-8bdf7d1a087c/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho=
|
||||
github.com/muesli/smartcrop v0.0.0-20171215203440-9032446b30f6 h1:m5kCp2hHRMu7z95f+aIK80l8N1tx+UXAqNoYpRS9v3o=
|
||||
github.com/muesli/smartcrop v0.0.0-20171215203440-9032446b30f6/go.mod h1:i2fCI/UorTfgEpPPLWiFBv4pye+YAG78RwcQLUkocpI=
|
||||
github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5 h1:BvoENQQU+fZ9uukda/RzCAL/191HHwJA5b13R6diVlY=
|
||||
github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||
github.com/peterbourgon/diskv v0.0.0-20171120014656-2973218375c3 h1:ZKRE3mqKoxObHs5oWjLnA1WxXhmlDDAVuE0VsuLIoNk=
|
||||
github.com/peterbourgon/diskv v0.0.0-20171120014656-2973218375c3/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rwcarlsen/goexif v0.0.0-20141222211634-41dad3aa0833 h1:jwa1bSL1nx8Yz7pOXkjXJCbKAffj5i2Rrl9ALRqSy6Y=
|
||||
github.com/rwcarlsen/goexif v0.0.0-20141222211634-41dad3aa0833/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
|
||||
github.com/satori/go.uuid v0.0.0-20180103174451-36e9d2ebbde5 h1:tfcGHuraNSEY9xRb9ckCMqMD7xAjzrYI1WpD7DA+nz8=
|
||||
github.com/satori/go.uuid v0.0.0-20180103174451-36e9d2ebbde5/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/smartystreets/assertions v0.0.0-20180820201707-7c9eb446e3cf h1:6V1qxN6Usn4jy8unvggSJz/NC790tefw8Zdy6OZS5co=
|
||||
github.com/smartystreets/assertions v0.0.0-20180820201707-7c9eb446e3cf/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a h1:JSvGDIbmil4Ui/dDdFBExb7/cmkNjyX5F97oglmvCDo=
|
||||
github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
|
||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
golang.org/x/image v0.0.0-20171214225156-12117c17ca67 h1:L8S612DBEq9tpLTxczHuhrf5il+KEdSha8b5A5njQMc=
|
||||
golang.org/x/image v0.0.0-20171214225156-12117c17ca67/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
|
||||
golang.org/x/net v0.0.0-20180201232540-b417086c80e9 h1:2srMuQrhUnBbRCQTYMFbAtec0kzANvdPwGdfLIXUodw=
|
||||
golang.org/x/net v0.0.0-20180201232540-b417086c80e9/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/oauth2 v0.0.0-20180126164932-a032972e2806 h1:KZrxuLRRjPt8IU4XrMl45WQaER+p8WGu9cN8EekYVzM=
|
||||
golang.org/x/oauth2 v0.0.0-20180126164932-a032972e2806/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/text v0.0.0-20171227012246-e19ae1496984 h1:ulYJn/BqO4fMRe1xAQzWjokgjsQLPpb21GltxXHI3fQ=
|
||||
golang.org/x/text v0.0.0-20171227012246-e19ae1496984/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
google.golang.org/api v0.0.0-20180202000329-f7618f4b41ca h1:bPw62MiF+sPyq2Apz3MT+qTY+hEgfVEUfkO4dVCK5oo=
|
||||
google.golang.org/api v0.0.0-20180202000329-f7618f4b41ca/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
google.golang.org/appengine v0.0.0-20171212223047-5bee14b453b4 h1:gggB/NnRSjJj9dpMAaxm4mMTFai1QLGL39CGXmQvr+s=
|
||||
google.golang.org/appengine v0.0.0-20171212223047-5bee14b453b4/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/genproto v0.0.0-20180125080656-4eb30f4778ee h1:z84t4XBifcyxT9HA96QiKwGaWYiO7vfoqu7vWoa6lQk=
|
||||
google.golang.org/genproto v0.0.0-20180125080656-4eb30f4778ee/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/grpc v0.0.0-20180201193814-f9628db66d14 h1:y8LqgDoOxiN/KHkwtrvzgzZtDvk5EAQCGBeWgpRUt1Q=
|
||||
google.golang.org/grpc v0.0.0-20180201193814-f9628db66d14/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
willnorris.com/go/gifresize v1.0.0 h1:KkdTSrFE1mhzAgTO8/ukK6ZP4SVptL4G3d1lx5X5qMw=
|
||||
willnorris.com/go/gifresize v1.0.0/go.mod h1:eBM8gogBGCcaH603vxSpnfjwXIpq6nmnj/jauBDKtAk=
|
||||
387
vendor/willnorris.com/go/imageproxy/imageproxy.go
сгенерированный
поставляемый
Обычный файл
387
vendor/willnorris.com/go/imageproxy/imageproxy.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,387 @@
|
||||
// Copyright 2013 Google Inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package imageproxy provides an image proxy server. For typical use of
|
||||
// creating and using a Proxy, see cmd/imageproxy/main.go.
|
||||
package imageproxy // import "willnorris.com/go/imageproxy"
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"mime"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gregjones/httpcache"
|
||||
tphttp "willnorris.com/go/imageproxy/third_party/http"
|
||||
)
|
||||
|
||||
// Proxy serves image requests.
|
||||
type Proxy struct {
|
||||
Client *http.Client // client used to fetch remote URLs
|
||||
Cache Cache // cache used to cache responses
|
||||
|
||||
// RemoteHosts specifies a list of remote hosts that images can be
|
||||
// proxied from. An empty list means all hosts are allowed.
|
||||
RemoteHosts []string
|
||||
|
||||
// Whitelist should no longer be used. Use "RemoteHosts" instead.
|
||||
Whitelist []string
|
||||
|
||||
// Referrers, when given, requires that requests to the image
|
||||
// proxy come from a referring host. An empty list means all
|
||||
// hosts are allowed.
|
||||
Referrers []string
|
||||
|
||||
// DefaultBaseURL is the URL that relative remote URLs are resolved in
|
||||
// reference to. If nil, all remote URLs specified in requests must be
|
||||
// absolute.
|
||||
DefaultBaseURL *url.URL
|
||||
|
||||
// SignatureKey is the HMAC key used to verify signed requests.
|
||||
SignatureKey []byte
|
||||
|
||||
// Allow images to scale beyond their original dimensions.
|
||||
ScaleUp bool
|
||||
|
||||
// Timeout specifies a time limit for requests served by this Proxy.
|
||||
// If a call runs for longer than its time limit, a 504 Gateway Timeout
|
||||
// response is returned. A Timeout of zero means no timeout.
|
||||
Timeout time.Duration
|
||||
|
||||
// If true, log additional debug messages
|
||||
Verbose bool
|
||||
|
||||
// ContentTypes specifies a list of content types to allow. An empty
|
||||
// list means all content types are allowed.
|
||||
ContentTypes []string
|
||||
}
|
||||
|
||||
// NewProxy constructs a new proxy. The provided http RoundTripper will be
|
||||
// used to fetch remote URLs. If nil is provided, http.DefaultTransport will
|
||||
// be used.
|
||||
func NewProxy(transport http.RoundTripper, cache Cache) *Proxy {
|
||||
if transport == nil {
|
||||
transport = http.DefaultTransport
|
||||
}
|
||||
if cache == nil {
|
||||
cache = NopCache
|
||||
}
|
||||
|
||||
proxy := &Proxy{
|
||||
Cache: cache,
|
||||
}
|
||||
|
||||
client := new(http.Client)
|
||||
client.Transport = &httpcache.Transport{
|
||||
Transport: &TransformingTransport{
|
||||
Transport: transport,
|
||||
CachingClient: client,
|
||||
log: func(format string, v ...interface{}) {
|
||||
if proxy.Verbose {
|
||||
log.Printf(format, v...)
|
||||
}
|
||||
},
|
||||
},
|
||||
Cache: cache,
|
||||
MarkCachedResponses: true,
|
||||
}
|
||||
|
||||
proxy.Client = client
|
||||
|
||||
return proxy
|
||||
}
|
||||
|
||||
// ServeHTTP handles incoming requests.
|
||||
func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/favicon.ico" {
|
||||
return // ignore favicon requests
|
||||
}
|
||||
|
||||
if r.URL.Path == "/" || r.URL.Path == "/health-check" {
|
||||
fmt.Fprint(w, "OK")
|
||||
return
|
||||
}
|
||||
|
||||
var h http.Handler = http.HandlerFunc(p.serveImage)
|
||||
if p.Timeout > 0 {
|
||||
h = tphttp.TimeoutHandler(h, p.Timeout, "Gateway timeout waiting for remote resource.")
|
||||
}
|
||||
h.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// serveImage handles incoming requests for proxied images.
|
||||
func (p *Proxy) serveImage(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := NewRequest(r, p.DefaultBaseURL)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("invalid request URL: %v", err)
|
||||
log.Print(msg)
|
||||
http.Error(w, msg, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// assign static settings from proxy to req.Options
|
||||
req.Options.ScaleUp = p.ScaleUp
|
||||
|
||||
if err := p.allowed(req); err != nil {
|
||||
log.Print(err)
|
||||
http.Error(w, err.Error(), http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := p.Client.Get(req.String())
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("error fetching remote image: %v", err)
|
||||
log.Print(msg)
|
||||
http.Error(w, msg, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
cached := resp.Header.Get(httpcache.XFromCache)
|
||||
if p.Verbose {
|
||||
log.Printf("request: %v (served from cache: %v)", *req, cached == "1")
|
||||
}
|
||||
|
||||
copyHeader(w.Header(), resp.Header, "Cache-Control", "Last-Modified", "Expires", "Etag", "Link")
|
||||
|
||||
if should304(r, resp) {
|
||||
w.WriteHeader(http.StatusNotModified)
|
||||
return
|
||||
}
|
||||
|
||||
contentType, _, _ := mime.ParseMediaType(resp.Header.Get("Content-Type"))
|
||||
if resp.ContentLength != 0 && !validContentType(p.ContentTypes, contentType) {
|
||||
msg := fmt.Sprintf("forbidden content-type: %q", contentType)
|
||||
log.Print(msg)
|
||||
http.Error(w, msg, http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", contentType)
|
||||
|
||||
copyHeader(w.Header(), resp.Header, "Content-Length")
|
||||
|
||||
//Enable CORS for 3rd party applications
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
|
||||
w.WriteHeader(resp.StatusCode)
|
||||
io.Copy(w, resp.Body)
|
||||
}
|
||||
|
||||
// copyHeader copies header values from src to dst, adding to any existing
|
||||
// values with the same header name. If keys is not empty, only those header
|
||||
// keys will be copied.
|
||||
func copyHeader(dst, src http.Header, keys ...string) {
|
||||
if len(keys) == 0 {
|
||||
for k, _ := range src {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
}
|
||||
for _, key := range keys {
|
||||
k := http.CanonicalHeaderKey(key)
|
||||
for _, v := range src[k] {
|
||||
dst.Add(k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// allowed determines whether the specified request contains an allowed
|
||||
// referrer, host, and signature. It returns an error if the request is not
|
||||
// allowed.
|
||||
func (p *Proxy) allowed(r *Request) error {
|
||||
if p.RemoteHosts == nil {
|
||||
// backwards compatible with old naming of the field
|
||||
p.RemoteHosts = p.Whitelist
|
||||
}
|
||||
if len(p.Referrers) > 0 && !validReferrer(p.Referrers, r.Original) {
|
||||
return fmt.Errorf("request does not contain an allowed referrer: %v", r)
|
||||
}
|
||||
|
||||
if len(p.RemoteHosts) == 0 && len(p.SignatureKey) == 0 {
|
||||
return nil // no allowed hosts or signature key, all requests accepted
|
||||
}
|
||||
|
||||
if len(p.RemoteHosts) > 0 && validHost(p.RemoteHosts, r.URL) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(p.SignatureKey) > 0 && validSignature(p.SignatureKey, r) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("request does not contain an allowed host or valid signature: %v", r)
|
||||
}
|
||||
|
||||
// validContentType returns whether contentType matches one of the allowed patterns.
|
||||
func validContentType(patterns []string, contentType string) bool {
|
||||
if len(patterns) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, pattern := range patterns {
|
||||
if ok, err := filepath.Match(pattern, contentType); ok && err == nil {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// validHost returns whether the host in u matches one of hosts.
|
||||
func validHost(hosts []string, u *url.URL) bool {
|
||||
for _, host := range hosts {
|
||||
if u.Host == host {
|
||||
return true
|
||||
}
|
||||
if strings.HasPrefix(host, "*.") && strings.HasSuffix(u.Host, host[2:]) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// returns whether the referrer from the request is in the host list.
|
||||
func validReferrer(hosts []string, r *http.Request) bool {
|
||||
u, err := url.Parse(r.Header.Get("Referer"))
|
||||
if err != nil { // malformed or blank header, just deny
|
||||
return false
|
||||
}
|
||||
|
||||
return validHost(hosts, u)
|
||||
}
|
||||
|
||||
// validSignature returns whether the request signature is valid.
|
||||
func validSignature(key []byte, r *Request) bool {
|
||||
sig := r.Options.Signature
|
||||
if m := len(sig) % 4; m != 0 { // add padding if missing
|
||||
sig += strings.Repeat("=", 4-m)
|
||||
}
|
||||
|
||||
got, err := base64.URLEncoding.DecodeString(sig)
|
||||
if err != nil {
|
||||
log.Printf("error base64 decoding signature %q", r.Options.Signature)
|
||||
return false
|
||||
}
|
||||
|
||||
mac := hmac.New(sha256.New, key)
|
||||
mac.Write([]byte(r.URL.String()))
|
||||
want := mac.Sum(nil)
|
||||
|
||||
return hmac.Equal(got, want)
|
||||
}
|
||||
|
||||
// should304 returns whether we should send a 304 Not Modified in response to
|
||||
// req, based on the response resp. This is determined using the last modified
|
||||
// time and the entity tag of resp.
|
||||
func should304(req *http.Request, resp *http.Response) bool {
|
||||
// TODO(willnorris): if-none-match header can be a comma separated list
|
||||
// of multiple tags to be matched, or the special value "*" which
|
||||
// matches all etags
|
||||
etag := resp.Header.Get("Etag")
|
||||
if etag != "" && etag == req.Header.Get("If-None-Match") {
|
||||
return true
|
||||
}
|
||||
|
||||
lastModified, err := time.Parse(time.RFC1123, resp.Header.Get("Last-Modified"))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
ifModSince, err := time.Parse(time.RFC1123, req.Header.Get("If-Modified-Since"))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if lastModified.Before(ifModSince) || lastModified.Equal(ifModSince) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// TransformingTransport is an implementation of http.RoundTripper that
|
||||
// optionally transforms images using the options specified in the request URL
|
||||
// fragment.
|
||||
type TransformingTransport struct {
|
||||
// Transport is the underlying http.RoundTripper used to satisfy
|
||||
// non-transform requests (those that do not include a URL fragment).
|
||||
Transport http.RoundTripper
|
||||
|
||||
// CachingClient is used to fetch images to be resized. This client is
|
||||
// used rather than Transport directly in order to ensure that
|
||||
// responses are properly cached.
|
||||
CachingClient *http.Client
|
||||
|
||||
log func(format string, v ...interface{})
|
||||
}
|
||||
|
||||
// RoundTrip implements the http.RoundTripper interface.
|
||||
func (t *TransformingTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
if req.URL.Fragment == "" {
|
||||
// normal requests pass through
|
||||
if t.log != nil {
|
||||
t.log("fetching remote URL: %v", req.URL)
|
||||
}
|
||||
return t.Transport.RoundTrip(req)
|
||||
}
|
||||
|
||||
u := *req.URL
|
||||
u.Fragment = ""
|
||||
resp, err := t.CachingClient.Get(u.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
if should304(req, resp) {
|
||||
// bare 304 response, full response will be used from cache
|
||||
return &http.Response{StatusCode: http.StatusNotModified}, nil
|
||||
}
|
||||
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
opt := ParseOptions(req.URL.Fragment)
|
||||
|
||||
img, err := Transform(b, opt)
|
||||
if err != nil {
|
||||
log.Printf("error transforming image %s: %v", u.String(), err)
|
||||
img = b
|
||||
}
|
||||
|
||||
// replay response with transformed image and updated content length
|
||||
buf := new(bytes.Buffer)
|
||||
fmt.Fprintf(buf, "%s %s\n", resp.Proto, resp.Status)
|
||||
resp.Header.WriteSubset(buf, map[string]bool{
|
||||
"Content-Length": true,
|
||||
// exclude Content-Type header if the format may have changed during transformation
|
||||
"Content-Type": opt.Format != "" || resp.Header.Get("Content-Type") == "image/webp" || resp.Header.Get("Content-Type") == "image/tiff",
|
||||
})
|
||||
fmt.Fprintf(buf, "Content-Length: %d\n\n", len(img))
|
||||
buf.Write(img)
|
||||
|
||||
return http.ReadResponse(bufio.NewReader(buf), req)
|
||||
}
|
||||
27
vendor/willnorris.com/go/imageproxy/third_party/http/LICENSE
сгенерированный
поставляемый
Обычный файл
27
vendor/willnorris.com/go/imageproxy/third_party/http/LICENSE
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,27 @@
|
||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
1
vendor/willnorris.com/go/imageproxy/third_party/http/README
сгенерированный
поставляемый
Обычный файл
1
vendor/willnorris.com/go/imageproxy/third_party/http/README
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1 @@
|
||||
Package http is based on a copy of net/http.
|
||||
136
vendor/willnorris.com/go/imageproxy/third_party/http/server.go
сгенерированный
поставляемый
Обычный файл
136
vendor/willnorris.com/go/imageproxy/third_party/http/server.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,136 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package http provides helpers for HTTP servers.
|
||||
package http
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TimeoutHandler returns a Handler that runs h with the given time limit.
|
||||
//
|
||||
// The new Handler calls h.ServeHTTP to handle each request, but if a
|
||||
// call runs for longer than its time limit, the handler responds with
|
||||
// a 504 Gateway Timeout error and the given message in its body.
|
||||
// (If msg is empty, a suitable default message will be sent.)
|
||||
// After such a timeout, writes by h to its ResponseWriter will return
|
||||
// ErrHandlerTimeout.
|
||||
//
|
||||
// TimeoutHandler buffers all Handler writes to memory and does not
|
||||
// support the Hijacker or Flusher interfaces.
|
||||
func TimeoutHandler(h http.Handler, dt time.Duration, msg string) http.Handler {
|
||||
return &timeoutHandler{
|
||||
handler: h,
|
||||
body: msg,
|
||||
dt: dt,
|
||||
}
|
||||
}
|
||||
|
||||
// ErrHandlerTimeout is returned on ResponseWriter Write calls
|
||||
// in handlers which have timed out.
|
||||
var ErrHandlerTimeout = errors.New("http: Handler timeout")
|
||||
|
||||
type timeoutHandler struct {
|
||||
handler http.Handler
|
||||
body string
|
||||
dt time.Duration
|
||||
|
||||
// When set, no timer will be created and this channel will
|
||||
// be used instead.
|
||||
testTimeout <-chan time.Time
|
||||
}
|
||||
|
||||
func (h *timeoutHandler) errorBody() string {
|
||||
if h.body != "" {
|
||||
return h.body
|
||||
}
|
||||
return "<html><head><title>Timeout</title></head><body><h1>Timeout</h1></body></html>"
|
||||
}
|
||||
|
||||
func (h *timeoutHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
var t *time.Timer
|
||||
timeout := h.testTimeout
|
||||
if timeout == nil {
|
||||
t = time.NewTimer(h.dt)
|
||||
timeout = t.C
|
||||
}
|
||||
done := make(chan struct{})
|
||||
tw := &timeoutWriter{
|
||||
w: w,
|
||||
h: make(http.Header),
|
||||
}
|
||||
go func() {
|
||||
h.handler.ServeHTTP(tw, r)
|
||||
close(done)
|
||||
}()
|
||||
select {
|
||||
case <-done:
|
||||
tw.mu.Lock()
|
||||
defer tw.mu.Unlock()
|
||||
dst := w.Header()
|
||||
for k, vv := range tw.h {
|
||||
dst[k] = vv
|
||||
}
|
||||
if !tw.wroteHeader {
|
||||
tw.code = http.StatusOK
|
||||
}
|
||||
w.WriteHeader(tw.code)
|
||||
w.Write(tw.wbuf.Bytes())
|
||||
if t != nil {
|
||||
t.Stop()
|
||||
}
|
||||
case <-timeout:
|
||||
tw.mu.Lock()
|
||||
defer tw.mu.Unlock()
|
||||
w.WriteHeader(http.StatusGatewayTimeout)
|
||||
io.WriteString(w, h.errorBody())
|
||||
tw.timedOut = true
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
type timeoutWriter struct {
|
||||
w http.ResponseWriter
|
||||
h http.Header
|
||||
wbuf bytes.Buffer
|
||||
|
||||
mu sync.Mutex
|
||||
timedOut bool
|
||||
wroteHeader bool
|
||||
code int
|
||||
}
|
||||
|
||||
func (tw *timeoutWriter) Header() http.Header { return tw.h }
|
||||
|
||||
func (tw *timeoutWriter) Write(p []byte) (int, error) {
|
||||
tw.mu.Lock()
|
||||
defer tw.mu.Unlock()
|
||||
if tw.timedOut {
|
||||
return 0, ErrHandlerTimeout
|
||||
}
|
||||
if !tw.wroteHeader {
|
||||
tw.writeHeader(http.StatusOK)
|
||||
}
|
||||
return tw.wbuf.Write(p)
|
||||
}
|
||||
|
||||
func (tw *timeoutWriter) WriteHeader(code int) {
|
||||
tw.mu.Lock()
|
||||
defer tw.mu.Unlock()
|
||||
if tw.timedOut || tw.wroteHeader {
|
||||
return
|
||||
}
|
||||
tw.writeHeader(code)
|
||||
}
|
||||
|
||||
func (tw *timeoutWriter) writeHeader(code int) {
|
||||
tw.wroteHeader = true
|
||||
tw.code = code
|
||||
}
|
||||
312
vendor/willnorris.com/go/imageproxy/transform.go
сгенерированный
поставляемый
Обычный файл
312
vendor/willnorris.com/go/imageproxy/transform.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,312 @@
|
||||
// Copyright 2013 Google Inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package imageproxy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"image"
|
||||
_ "image/gif" // register gif format
|
||||
"image/jpeg"
|
||||
"image/png"
|
||||
"io"
|
||||
"log"
|
||||
"math"
|
||||
|
||||
"github.com/disintegration/imaging"
|
||||
"github.com/muesli/smartcrop"
|
||||
"github.com/rwcarlsen/goexif/exif"
|
||||
"golang.org/x/image/tiff" // register tiff format
|
||||
_ "golang.org/x/image/webp" // register webp format
|
||||
"willnorris.com/go/gifresize"
|
||||
)
|
||||
|
||||
// default compression quality of resized jpegs
|
||||
const defaultQuality = 95
|
||||
|
||||
// maximum distance into image to look for EXIF tags
|
||||
const maxExifSize = 1 << 20
|
||||
|
||||
// resample filter used when resizing images
|
||||
var resampleFilter = imaging.Lanczos
|
||||
|
||||
// Transform the provided image. img should contain the raw bytes of an
|
||||
// encoded image in one of the supported formats (gif, jpeg, or png). The
|
||||
// bytes of a similarly encoded image is returned.
|
||||
func Transform(img []byte, opt Options) ([]byte, error) {
|
||||
if !opt.transform() {
|
||||
// bail if no transformation was requested
|
||||
return img, nil
|
||||
}
|
||||
|
||||
// decode image
|
||||
m, format, err := image.Decode(bytes.NewReader(img))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// apply EXIF orientation for jpeg and tiff source images. Read at most
|
||||
// up to maxExifSize looking for EXIF tags.
|
||||
if format == "jpeg" || format == "tiff" {
|
||||
r := io.LimitReader(bytes.NewReader(img), maxExifSize)
|
||||
if exifOpt := exifOrientation(r); exifOpt.transform() {
|
||||
m = transformImage(m, exifOpt)
|
||||
}
|
||||
}
|
||||
|
||||
// encode webp and tiff as jpeg by default
|
||||
if format == "tiff" || format == "webp" {
|
||||
format = "jpeg"
|
||||
}
|
||||
|
||||
if opt.Format != "" {
|
||||
format = opt.Format
|
||||
}
|
||||
|
||||
// transform and encode image
|
||||
buf := new(bytes.Buffer)
|
||||
switch format {
|
||||
case "gif":
|
||||
fn := func(img image.Image) image.Image {
|
||||
return transformImage(img, opt)
|
||||
}
|
||||
err = gifresize.Process(buf, bytes.NewReader(img), fn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "jpeg":
|
||||
quality := opt.Quality
|
||||
if quality == 0 {
|
||||
quality = defaultQuality
|
||||
}
|
||||
|
||||
m = transformImage(m, opt)
|
||||
err = jpeg.Encode(buf, m, &jpeg.Options{Quality: quality})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "png":
|
||||
m = transformImage(m, opt)
|
||||
err = png.Encode(buf, m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "tiff":
|
||||
m = transformImage(m, opt)
|
||||
err = tiff.Encode(buf, m, &tiff.Options{tiff.Deflate, true})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported format: %v", format)
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// evaluateFloat interprets the option value f. If f is between 0 and 1, it is
|
||||
// interpreted as a percentage of max, otherwise it is treated as an absolute
|
||||
// value. If f is less than 0, 0 is returned.
|
||||
func evaluateFloat(f float64, max int) int {
|
||||
if 0 < f && f < 1 {
|
||||
return int(float64(max) * f)
|
||||
}
|
||||
if f < 0 {
|
||||
return 0
|
||||
}
|
||||
return int(f)
|
||||
}
|
||||
|
||||
// resizeParams determines if the image needs to be resized, and if so, the
|
||||
// dimensions to resize to.
|
||||
func resizeParams(m image.Image, opt Options) (w, h int, resize bool) {
|
||||
// convert percentage width and height values to absolute values
|
||||
imgW := m.Bounds().Dx()
|
||||
imgH := m.Bounds().Dy()
|
||||
w = evaluateFloat(opt.Width, imgW)
|
||||
h = evaluateFloat(opt.Height, imgH)
|
||||
|
||||
// never resize larger than the original image unless specifically allowed
|
||||
if !opt.ScaleUp {
|
||||
if w > imgW {
|
||||
w = imgW
|
||||
}
|
||||
if h > imgH {
|
||||
h = imgH
|
||||
}
|
||||
}
|
||||
|
||||
// if requested width and height match the original, skip resizing
|
||||
if (w == imgW || w == 0) && (h == imgH || h == 0) {
|
||||
return 0, 0, false
|
||||
}
|
||||
|
||||
return w, h, true
|
||||
}
|
||||
|
||||
// cropParams calculates crop rectangle parameters to keep it in image bounds
|
||||
func cropParams(m image.Image, opt Options) image.Rectangle {
|
||||
if !opt.SmartCrop && opt.CropX == 0 && opt.CropY == 0 && opt.CropWidth == 0 && opt.CropHeight == 0 {
|
||||
return m.Bounds()
|
||||
}
|
||||
|
||||
// width and height of image
|
||||
imgW := m.Bounds().Dx()
|
||||
imgH := m.Bounds().Dy()
|
||||
|
||||
if opt.SmartCrop {
|
||||
w := evaluateFloat(opt.Width, imgW)
|
||||
h := evaluateFloat(opt.Height, imgH)
|
||||
log.Printf("smartcrop input: %dx%d", w, h)
|
||||
r, err := smartcrop.SmartCrop(m, w, h)
|
||||
if err != nil {
|
||||
log.Printf("error with smartcrop: %v", err)
|
||||
} else {
|
||||
log.Printf("smartcrop rectangle: %v", r)
|
||||
return r
|
||||
}
|
||||
}
|
||||
|
||||
// top left coordinate of crop
|
||||
x0 := evaluateFloat(math.Abs(opt.CropX), imgW)
|
||||
if opt.CropX < 0 {
|
||||
x0 = imgW - x0 // measure from right
|
||||
}
|
||||
y0 := evaluateFloat(math.Abs(opt.CropY), imgH)
|
||||
if opt.CropY < 0 {
|
||||
y0 = imgH - y0 // measure from bottom
|
||||
}
|
||||
|
||||
// width and height of crop
|
||||
w := evaluateFloat(opt.CropWidth, imgW)
|
||||
if w == 0 {
|
||||
w = imgW
|
||||
}
|
||||
h := evaluateFloat(opt.CropHeight, imgH)
|
||||
if h == 0 {
|
||||
h = imgH
|
||||
}
|
||||
|
||||
// bottom right coordinate of crop
|
||||
x1 := x0 + w
|
||||
if x1 > imgW {
|
||||
x1 = imgW
|
||||
}
|
||||
y1 := y0 + h
|
||||
if y1 > imgH {
|
||||
y1 = imgH
|
||||
}
|
||||
|
||||
return image.Rect(x0, y0, x1, y1)
|
||||
}
|
||||
|
||||
// read EXIF orientation tag from r and adjust opt to orient image correctly.
|
||||
func exifOrientation(r io.Reader) (opt Options) {
|
||||
// Exif Orientation Tag values
|
||||
// http://sylvana.net/jpegcrop/exif_orientation.html
|
||||
const (
|
||||
topLeftSide = 1
|
||||
topRightSide = 2
|
||||
bottomRightSide = 3
|
||||
bottomLeftSide = 4
|
||||
leftSideTop = 5
|
||||
rightSideTop = 6
|
||||
rightSideBottom = 7
|
||||
leftSideBottom = 8
|
||||
)
|
||||
|
||||
ex, err := exif.Decode(r)
|
||||
if err != nil {
|
||||
return opt
|
||||
}
|
||||
tag, err := ex.Get(exif.Orientation)
|
||||
if err != nil {
|
||||
return opt
|
||||
}
|
||||
orient, err := tag.Int(0)
|
||||
if err != nil {
|
||||
return opt
|
||||
}
|
||||
|
||||
switch orient {
|
||||
case topLeftSide:
|
||||
// do nothing
|
||||
case topRightSide:
|
||||
opt.FlipHorizontal = true
|
||||
case bottomRightSide:
|
||||
opt.Rotate = 180
|
||||
case bottomLeftSide:
|
||||
opt.FlipVertical = true
|
||||
case leftSideTop:
|
||||
opt.Rotate = 90
|
||||
opt.FlipVertical = true
|
||||
case rightSideTop:
|
||||
opt.Rotate = -90
|
||||
case rightSideBottom:
|
||||
opt.Rotate = 90
|
||||
opt.FlipHorizontal = true
|
||||
case leftSideBottom:
|
||||
opt.Rotate = 90
|
||||
}
|
||||
return opt
|
||||
}
|
||||
|
||||
// transformImage modifies the image m based on the transformations specified
|
||||
// in opt.
|
||||
func transformImage(m image.Image, opt Options) image.Image {
|
||||
// Parse crop and resize parameters before applying any transforms.
|
||||
// This is to ensure that any percentage-based values are based off the
|
||||
// size of the original image.
|
||||
rect := cropParams(m, opt)
|
||||
w, h, resize := resizeParams(m, opt)
|
||||
|
||||
// crop if needed
|
||||
if !m.Bounds().Eq(rect) {
|
||||
m = imaging.Crop(m, rect)
|
||||
}
|
||||
// resize if needed
|
||||
if resize {
|
||||
if opt.Fit {
|
||||
m = imaging.Fit(m, w, h, resampleFilter)
|
||||
} else {
|
||||
if w == 0 || h == 0 {
|
||||
m = imaging.Resize(m, w, h, resampleFilter)
|
||||
} else {
|
||||
m = imaging.Thumbnail(m, w, h, resampleFilter)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// rotate
|
||||
rotate := float64(opt.Rotate) - math.Floor(float64(opt.Rotate)/360)*360
|
||||
switch rotate {
|
||||
case 90:
|
||||
m = imaging.Rotate90(m)
|
||||
case 180:
|
||||
m = imaging.Rotate180(m)
|
||||
case 270:
|
||||
m = imaging.Rotate270(m)
|
||||
}
|
||||
|
||||
// flip
|
||||
if opt.FlipVertical {
|
||||
m = imaging.FlipV(m)
|
||||
}
|
||||
if opt.FlipHorizontal {
|
||||
m = imaging.FlipH(m)
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
Ссылка в новой задаче
Block a user