add ipa qrcode generate support
This commit is contained in:
@@ -5,18 +5,21 @@ import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"mime"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
type HTTPStaticServer struct {
|
||||
Root string
|
||||
Theme string
|
||||
Upload bool
|
||||
Root string
|
||||
Theme string
|
||||
Upload bool
|
||||
PlistProxy string
|
||||
|
||||
m *mux.Router
|
||||
}
|
||||
@@ -34,6 +37,7 @@ func NewHTTPStaticServer(root string) *HTTPStaticServer {
|
||||
m.HandleFunc("/-/status", s.hStatus)
|
||||
m.HandleFunc("/-/raw/{path:.*}", s.hFileOrDirectory)
|
||||
m.HandleFunc("/-/zip/{path:.*}", s.hZip)
|
||||
m.HandleFunc("/-/unzip/{zip_path:.*}/-/{path:.*}", s.hUnzip)
|
||||
m.HandleFunc("/-/json/{path:.*}", s.hJSONList)
|
||||
// routers for Apple *.ipa
|
||||
m.HandleFunc("/-/ipa/icon/{path:.*}", s.hIpaIcon)
|
||||
@@ -101,7 +105,8 @@ func (s *HTTPStaticServer) hIndex(w http.ResponseWriter, r *http.Request) {
|
||||
relPath := filepath.Join(s.Root, path)
|
||||
finfo, err := os.Stat(relPath)
|
||||
if err == nil && finfo.IsDir() {
|
||||
tmpl.Execute(w, s)
|
||||
tmpl.ExecuteTemplate(w, "index", s)
|
||||
// tmpl.Execute(w, s)
|
||||
} else {
|
||||
http.ServeFile(w, r, relPath)
|
||||
}
|
||||
@@ -112,7 +117,22 @@ func (s *HTTPStaticServer) hZip(w http.ResponseWriter, r *http.Request) {
|
||||
CompressToZip(w, filepath.Join(s.Root, path))
|
||||
}
|
||||
|
||||
func (s *HTTPStaticServer) hUnzip(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
zipPath, path := vars["zip_path"], vars["path"]
|
||||
ctype := mime.TypeByExtension(filepath.Ext(path))
|
||||
if ctype != "" {
|
||||
w.Header().Set("Content-Type", ctype)
|
||||
}
|
||||
err := ExtractFromZip(filepath.Join(s.Root, zipPath), path, w)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (s *HTTPStaticServer) hIpaIcon(w http.ResponseWriter, r *http.Request) {
|
||||
// Useless now.
|
||||
path := mux.Vars(r)["path"]
|
||||
relPath := filepath.Join(s.Root, path)
|
||||
data, err := parseIpaIcon(relPath)
|
||||
@@ -124,6 +144,18 @@ func (s *HTTPStaticServer) hIpaIcon(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write(data)
|
||||
}
|
||||
|
||||
func genURLStr(r *http.Request, path string) *url.URL {
|
||||
scheme := "http"
|
||||
if r.TLS != nil {
|
||||
scheme = "https"
|
||||
}
|
||||
return &url.URL{
|
||||
Scheme: scheme,
|
||||
Host: r.Host,
|
||||
Path: path,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *HTTPStaticServer) hPlist(w http.ResponseWriter, r *http.Request) {
|
||||
path := mux.Vars(r)["path"]
|
||||
// rename *.plist to *.ipa
|
||||
@@ -137,22 +169,16 @@ func (s *HTTPStaticServer) hPlist(w http.ResponseWriter, r *http.Request) {
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
}
|
||||
|
||||
scheme := "http"
|
||||
if r.TLS != nil {
|
||||
scheme = "https"
|
||||
}
|
||||
ipaURL := url.URL{
|
||||
baseURL := &url.URL{
|
||||
Scheme: scheme,
|
||||
Host: r.Host,
|
||||
Path: path,
|
||||
}
|
||||
imgURL := url.URL{
|
||||
Scheme: scheme,
|
||||
Host: r.Host,
|
||||
Path: filepath.Join("/-/ipa/icon", path),
|
||||
}
|
||||
// TODO: image ignore here.
|
||||
data, err := generateDownloadPlist(ipaURL.String(), imgURL.String(), plinfo)
|
||||
data, err := generateDownloadPlist(baseURL, path, plinfo)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
@@ -162,12 +188,20 @@ func (s *HTTPStaticServer) hPlist(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func (s *HTTPStaticServer) hIpaLink(w http.ResponseWriter, r *http.Request) {
|
||||
// need ua_parser
|
||||
w.Write([]byte("redirect to itms service")) // url need urjJoin if setted plist proxy
|
||||
path := mux.Vars(r)["path"]
|
||||
plistUrl := genURLStr(r, "/-/ipa/plist/"+path).String()
|
||||
if s.PlistProxy != "" {
|
||||
plistUrl = strings.TrimSuffix(s.PlistProxy, "/") + "/" + r.Host + "/-/ipa/plist/" + path
|
||||
}
|
||||
|
||||
// WeChat need to tell to open with Safari
|
||||
|
||||
// Browser just tell not supported.
|
||||
w.Header().Set("Content-Type", "text/html")
|
||||
tmpl.ExecuteTemplate(w, "ipa-install", map[string]string{
|
||||
"Name": filepath.Base(path),
|
||||
"PlistLink": plistUrl,
|
||||
})
|
||||
// w.Write([]byte(fmt.Sprintf(
|
||||
// `<a href='itms-services://?action=download-manifest&url=%s'>Click this link to install</a>`,
|
||||
// plistUrl)))
|
||||
}
|
||||
|
||||
func (s *HTTPStaticServer) hFileOrDirectory(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
276
ipa.go
276
ipa.go
@@ -1,133 +1,143 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
|
||||
goplist "github.com/DHowett/go-plist"
|
||||
)
|
||||
|
||||
type plistBundle struct {
|
||||
CFBundleIdentifier string `plist:"CFBundleIdentifier"`
|
||||
CFBundleVersion string `plist:"CFBundleVersion"`
|
||||
CFBundleDisplayName string `plist:"CFBundleDisplayName"`
|
||||
CFBundleIconFile string `plist:"CFBundleIconFile"`
|
||||
}
|
||||
|
||||
func parseIpaIcon(path string) (data []byte, err error) {
|
||||
iconPattern := regexp.MustCompile(`(?i)^Payload/[^/]*/icon\.png$`)
|
||||
r, err := zip.OpenReader(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
var zfile *zip.File
|
||||
for _, file := range r.File {
|
||||
if iconPattern.MatchString(file.Name) {
|
||||
zfile = file
|
||||
break
|
||||
}
|
||||
}
|
||||
if zfile == nil {
|
||||
err = errors.New("icon.png file not found")
|
||||
return
|
||||
}
|
||||
plreader, err := zfile.Open()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer plreader.Close()
|
||||
return ioutil.ReadAll(plreader)
|
||||
}
|
||||
|
||||
func parseIPA(path string) (plinfo *plistBundle, err error) {
|
||||
plistre := regexp.MustCompile(`^Payload/[^/]*/Info\.plist$`)
|
||||
r, err := zip.OpenReader(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
var plfile *zip.File
|
||||
for _, file := range r.File {
|
||||
if plistre.MatchString(file.Name) {
|
||||
plfile = file
|
||||
break
|
||||
}
|
||||
}
|
||||
if plfile == nil {
|
||||
err = errors.New("Info.plist file not found")
|
||||
return
|
||||
}
|
||||
plreader, err := plfile.Open()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer plreader.Close()
|
||||
buf := make([]byte, plfile.FileInfo().Size())
|
||||
_, err = io.ReadFull(plreader, buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
dec := goplist.NewDecoder(bytes.NewReader(buf))
|
||||
plinfo = new(plistBundle)
|
||||
err = dec.Decode(plinfo)
|
||||
return
|
||||
}
|
||||
|
||||
// ref: https://gist.github.com/frischmilch/b15d81eabb67925642bd#file_manifest.plist
|
||||
type plAsset struct {
|
||||
Kind string `plist:"kind"`
|
||||
URL string `plist:"url"`
|
||||
}
|
||||
|
||||
type plItem struct {
|
||||
Assets []*plAsset `plist:"assets"`
|
||||
Metadata struct {
|
||||
BundleIdentifier string `plist:"bundle-identifier"`
|
||||
BundleVersion string `plist:"bundle-version"`
|
||||
Kind string `plist:"kind"`
|
||||
Title string `plist:"title"`
|
||||
} `plist:"metadata"`
|
||||
}
|
||||
|
||||
type downloadPlist struct {
|
||||
Items []*plItem `plist:"items"`
|
||||
}
|
||||
|
||||
func generateDownloadPlist(ipaUrl, imgUrl string, plinfo *plistBundle) ([]byte, error) {
|
||||
dp := new(downloadPlist)
|
||||
item := new(plItem)
|
||||
item.Assets = append(item.Assets, &plAsset{
|
||||
Kind: "software-package",
|
||||
URL: ipaUrl,
|
||||
})
|
||||
|
||||
// FIXME(ssx): find icon from CFBundleIconFile
|
||||
_ = imgUrl
|
||||
// , &plAsset{
|
||||
// Kind: "display-image",
|
||||
// URL: imgUrl,
|
||||
// })
|
||||
|
||||
item.Metadata.Kind = "software"
|
||||
|
||||
item.Metadata.BundleIdentifier = plinfo.CFBundleIdentifier
|
||||
item.Metadata.BundleVersion = plinfo.CFBundleVersion
|
||||
item.Metadata.Title = plinfo.CFBundleDisplayName
|
||||
if item.Metadata.Title == "" {
|
||||
item.Metadata.Title = filepath.Base(ipaUrl)
|
||||
}
|
||||
|
||||
dp.Items = append(dp.Items, item)
|
||||
data, err := goplist.MarshalIndent(dp, goplist.XMLFormat, " ")
|
||||
// fmt.Println(string(data))
|
||||
// fmt.Println(err)
|
||||
return data, err
|
||||
}
|
||||
package main
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
|
||||
goplist "github.com/DHowett/go-plist"
|
||||
)
|
||||
|
||||
func parseIpaIcon(path string) (data []byte, err error) {
|
||||
iconPattern := regexp.MustCompile(`(?i)^Payload/[^/]*/icon\.png$`)
|
||||
r, err := zip.OpenReader(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
var zfile *zip.File
|
||||
for _, file := range r.File {
|
||||
if iconPattern.MatchString(file.Name) {
|
||||
zfile = file
|
||||
break
|
||||
}
|
||||
}
|
||||
if zfile == nil {
|
||||
err = errors.New("icon.png file not found")
|
||||
return
|
||||
}
|
||||
plreader, err := zfile.Open()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer plreader.Close()
|
||||
return ioutil.ReadAll(plreader)
|
||||
}
|
||||
|
||||
func parseIPA(path string) (plinfo *plistBundle, err error) {
|
||||
plistre := regexp.MustCompile(`^Payload/[^/]*/Info\.plist$`)
|
||||
r, err := zip.OpenReader(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
var plfile *zip.File
|
||||
for _, file := range r.File {
|
||||
if plistre.MatchString(file.Name) {
|
||||
plfile = file
|
||||
break
|
||||
}
|
||||
}
|
||||
if plfile == nil {
|
||||
err = errors.New("Info.plist file not found")
|
||||
return
|
||||
}
|
||||
plreader, err := plfile.Open()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer plreader.Close()
|
||||
buf := make([]byte, plfile.FileInfo().Size())
|
||||
_, err = io.ReadFull(plreader, buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
dec := goplist.NewDecoder(bytes.NewReader(buf))
|
||||
plinfo = new(plistBundle)
|
||||
err = dec.Decode(plinfo)
|
||||
return
|
||||
}
|
||||
|
||||
type plistBundle struct {
|
||||
CFBundleIdentifier string `plist:"CFBundleIdentifier"`
|
||||
CFBundleVersion string `plist:"CFBundleVersion"`
|
||||
CFBundleDisplayName string `plist:"CFBundleDisplayName"`
|
||||
CFBundleName string `plist:"CFBundleName"`
|
||||
CFBundleIconFile string `plist:"CFBundleIconFile"`
|
||||
CFBundleIcons struct {
|
||||
CFBundlePrimaryIcon struct {
|
||||
CFBundleIconFiles []string `plist:"CFBundleIconFiles"`
|
||||
} `plist:"CFBundlePrimaryIcon"`
|
||||
} `plist:"CFBundleIcons"`
|
||||
}
|
||||
|
||||
// ref: https://gist.github.com/frischmilch/b15d81eabb67925642bd#file_manifest.plist
|
||||
type plAsset struct {
|
||||
Kind string `plist:"kind"`
|
||||
URL string `plist:"url"`
|
||||
}
|
||||
|
||||
type plItem struct {
|
||||
Assets []*plAsset `plist:"assets"`
|
||||
Metadata struct {
|
||||
BundleIdentifier string `plist:"bundle-identifier"`
|
||||
BundleVersion string `plist:"bundle-version"`
|
||||
Kind string `plist:"kind"`
|
||||
Title string `plist:"title"`
|
||||
} `plist:"metadata"`
|
||||
}
|
||||
|
||||
type downloadPlist struct {
|
||||
Items []*plItem `plist:"items"`
|
||||
}
|
||||
|
||||
func generateDownloadPlist(baseURL *url.URL, ipaPath string, plinfo *plistBundle) ([]byte, error) {
|
||||
dp := new(downloadPlist)
|
||||
item := new(plItem)
|
||||
baseURL.Path = ipaPath
|
||||
ipaUrl := baseURL.String()
|
||||
item.Assets = append(item.Assets, &plAsset{
|
||||
Kind: "software-package",
|
||||
URL: ipaUrl,
|
||||
})
|
||||
|
||||
iconFiles := plinfo.CFBundleIcons.CFBundlePrimaryIcon.CFBundleIconFiles
|
||||
if iconFiles != nil && len(iconFiles) > 0 {
|
||||
baseURL.Path = "/-/unzip/" + ipaPath + "/-/**/" + iconFiles[0] + ".png"
|
||||
imgUrl := baseURL.String()
|
||||
item.Assets = append(item.Assets, &plAsset{
|
||||
Kind: "display-image",
|
||||
URL: imgUrl,
|
||||
})
|
||||
}
|
||||
|
||||
item.Metadata.Kind = "software"
|
||||
|
||||
item.Metadata.BundleIdentifier = plinfo.CFBundleIdentifier
|
||||
item.Metadata.BundleVersion = plinfo.CFBundleVersion
|
||||
item.Metadata.Title = plinfo.CFBundleName
|
||||
if item.Metadata.Title == "" {
|
||||
item.Metadata.Title = filepath.Base(ipaUrl)
|
||||
}
|
||||
|
||||
dp.Items = append(dp.Items, item)
|
||||
data, err := goplist.MarshalIndent(dp, goplist.XMLFormat, " ")
|
||||
return data, err
|
||||
}
|
||||
|
||||
7
main.go
7
main.go
@@ -25,7 +25,9 @@ type Configure struct {
|
||||
PlistProxy *url.URL
|
||||
}
|
||||
|
||||
var gcfg = Configure{}
|
||||
var (
|
||||
gcfg = Configure{}
|
||||
)
|
||||
|
||||
func parseFlags() {
|
||||
kingpin.HelpFlag.Short('h')
|
||||
@@ -52,6 +54,9 @@ func main() {
|
||||
if gcfg.Upload {
|
||||
ss.EnableUpload()
|
||||
}
|
||||
if gcfg.PlistProxy != nil {
|
||||
ss.PlistProxy = gcfg.PlistProxy.String()
|
||||
}
|
||||
|
||||
var hdlr http.Handler = ss
|
||||
// HTTP Basic Authentication
|
||||
|
||||
24
res.go
Normal file
24
res.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package main
|
||||
|
||||
import "html/template"
|
||||
|
||||
var (
|
||||
tmpl *template.Template
|
||||
templates = map[string]string{
|
||||
"index": "res/index.tmpl.html",
|
||||
"ipa-install": "res/ipa-install.tmpl.html",
|
||||
}
|
||||
)
|
||||
|
||||
func ParseTemplate(name string, content string) {
|
||||
if tmpl == nil {
|
||||
tmpl = template.New(name)
|
||||
}
|
||||
var t *template.Template
|
||||
if tmpl.Name() == name {
|
||||
t = tmpl
|
||||
} else {
|
||||
t = tmpl.New(name)
|
||||
}
|
||||
template.Must(t.New(name).Delims("[[", "]]").Parse(content))
|
||||
}
|
||||
BIN
res/imgs/wx.png
Normal file
BIN
res/imgs/wx.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
71
res/ipa-install.tmpl.html
Normal file
71
res/ipa-install.tmpl.html
Normal file
@@ -0,0 +1,71 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>[[.Name]] install</title>
|
||||
<meta http-equiv="Content-Type" content="text/HTML; charset=utf-8">
|
||||
<meta content="target-densitydpi=device-dpi,width=640" name="viewport" id="viewport">
|
||||
<link rel="shortcut icon" type="image/png" href="/-/res/favicon.png" />
|
||||
<script type="text/javascript" src="/-/res/js/ua-parser.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
function showById(name) {
|
||||
document.getElementById(name).style.display = 'block';
|
||||
}
|
||||
|
||||
function checkBrowerAndDownload() {
|
||||
var parser = new UAParser();
|
||||
var os_info = parser.getOS();
|
||||
console.log(os_info)
|
||||
|
||||
if (navigator.userAgent.toLowerCase().match(/MicroMessenger/i) == "micromessenger") {
|
||||
showById('wechat');
|
||||
return;
|
||||
}
|
||||
var plistLink = "[[.PlistLink]]";
|
||||
var ipaInstallLink = 'itms-services://?action=download-manifest&url=' + plistLink;
|
||||
|
||||
document.getElementById('itms-link').href = ipaInstallLink;
|
||||
|
||||
if (os_info.name == 'Android') {
|
||||
return;
|
||||
} else if (os_info.name == 'iOS') {
|
||||
showById('safari');
|
||||
|
||||
location.href = ipaInstallLink;
|
||||
return;
|
||||
} else {
|
||||
showById('browser');
|
||||
return;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<style>
|
||||
#wechat {
|
||||
position: relative;
|
||||
width: 640px;
|
||||
margin: 0 auto;
|
||||
background: #fff;
|
||||
overflow: hidden;
|
||||
min-height: 777px;
|
||||
}
|
||||
</style>
|
||||
<div id="wechat" style="display: none">
|
||||
<img style='width: 100%;position: relative;' src='/-/res/imgs/wx.png' />
|
||||
</div>
|
||||
<div id="browser" style="display: none">
|
||||
This is IPA install page, you should open this link with your iPhone.
|
||||
</div>
|
||||
<div id="safari" style="display: none">
|
||||
If install not started soon, click <a id="itms-link" href="#">here</a>
|
||||
</div>
|
||||
<div id="android" style="display: none">
|
||||
This is IPA install page, not for android.
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
checkBrowerAndDownload();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -82,9 +82,14 @@ var vm = new Vue({
|
||||
this.showHidden = !this.showHidden;
|
||||
},
|
||||
genQrcode: function(text) {
|
||||
var urlPath = location.protocol + "//" + location.host + location.pathname + text;
|
||||
var urlPath = location.protocol + "//" + pathJoin([location.host, location.pathname, text]);
|
||||
$("#qrcode-title").html(text);
|
||||
$("#qrcode-link").attr("href", urlPath);
|
||||
|
||||
if (getExtention(text) == "ipa") {
|
||||
urlPath = location.protocol + "//" + pathJoin([location.host, "/-/ipa/link", location.pathname, text]);
|
||||
console.log(urlPath)
|
||||
}
|
||||
$('#qrcodeCanvas').empty().qrcode({
|
||||
text: urlPath
|
||||
});
|
||||
|
||||
9
res/js/ua-parser.min.js
vendored
Normal file
9
res/js/ua-parser.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -1,17 +1,20 @@
|
||||
// +build bindata
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
var tmpl *template.Template
|
||||
|
||||
func init() {
|
||||
http.Handle("/-/res/", http.StripPrefix("/-/res/", http.FileServer(assetFS())))
|
||||
|
||||
indexContent, _ := Asset("res/index.tmpl.html")
|
||||
tmpl = template.Must(template.New("t").Delims("[[", "]]").Parse(string(indexContent)))
|
||||
}
|
||||
// +build bindata
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func init() {
|
||||
http.Handle("/-/res/", http.StripPrefix("/-/res/", http.FileServer(assetFS())))
|
||||
|
||||
for name, path := range templates {
|
||||
data, err := Asset(path)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
ParseTemplate(name, string(data))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
// +build !bindata
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
var tmpl *template.Template
|
||||
|
||||
func init() {
|
||||
http.Handle("/-/res/", http.StripPrefix("/-/res/", http.FileServer(http.Dir("./res"))))
|
||||
|
||||
indexContent, _ := ioutil.ReadFile("./res/index.tmpl.html")
|
||||
tmpl = template.Must(template.New("t").Delims("[[", "]]").Parse(string(indexContent)))
|
||||
}
|
||||
// +build !bindata
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func init() {
|
||||
http.Handle("/-/res/", http.StripPrefix("/-/res/", http.FileServer(http.Dir("./res"))))
|
||||
|
||||
for name, path := range templates {
|
||||
content, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
ParseTemplate(name, string(content))
|
||||
}
|
||||
}
|
||||
|
||||
221
zip.go
221
zip.go
@@ -1,92 +1,129 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Zip struct {
|
||||
*zip.Writer
|
||||
}
|
||||
|
||||
func sanitizedName(filename string) string {
|
||||
if len(filename) > 1 && filename[1] == ':' &&
|
||||
runtime.GOOS == "windows" {
|
||||
filename = filename[2:]
|
||||
}
|
||||
filename = strings.TrimLeft(strings.Replace(filename, `\`, "/", -1), `/`)
|
||||
filename = filepath.ToSlash(filename)
|
||||
filename = filepath.Clean(filename)
|
||||
return filename
|
||||
}
|
||||
|
||||
func statFile(filename string) (info os.FileInfo, reader io.ReadCloser, err error) {
|
||||
info, err = os.Lstat(filename)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// content
|
||||
if info.Mode()&os.ModeSymlink != 0 {
|
||||
var target string
|
||||
target, err = os.Readlink(filename)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
reader = ioutil.NopCloser(bytes.NewBuffer([]byte(target)))
|
||||
} else if !info.IsDir() {
|
||||
reader, err = os.Open(filename)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
reader = ioutil.NopCloser(bytes.NewBuffer(nil))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (z *Zip) Add(relpath, abspath string) error {
|
||||
info, rdc, err := statFile(abspath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rdc.Close()
|
||||
|
||||
hdr, err := zip.FileInfoHeader(info)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hdr.Name = sanitizedName(relpath)
|
||||
if info.IsDir() {
|
||||
hdr.Name += "/"
|
||||
}
|
||||
hdr.Method = zip.Deflate // compress method
|
||||
writer, err := z.CreateHeader(hdr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = io.Copy(writer, rdc)
|
||||
return err
|
||||
}
|
||||
|
||||
func CompressToZip(w http.ResponseWriter, rootDir string) {
|
||||
rootDir = filepath.Clean(rootDir)
|
||||
zipFileName := filepath.Base(rootDir) + ".zip"
|
||||
|
||||
w.Header().Set("Content-Type", "application/zip")
|
||||
w.Header().Set("Content-Disposition", `attachment; filename="`+zipFileName+`"`)
|
||||
|
||||
zw := &Zip{Writer: zip.NewWriter(w)}
|
||||
defer zw.Close()
|
||||
|
||||
filepath.Walk(rootDir, func(path string, info os.FileInfo, err error) error {
|
||||
zipPath := path[len(rootDir):]
|
||||
return zw.Add(zipPath, path)
|
||||
})
|
||||
}
|
||||
package main
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
dkignore "github.com/codeskyblue/dockerignore"
|
||||
)
|
||||
|
||||
type Zip struct {
|
||||
*zip.Writer
|
||||
}
|
||||
|
||||
func sanitizedName(filename string) string {
|
||||
if len(filename) > 1 && filename[1] == ':' &&
|
||||
runtime.GOOS == "windows" {
|
||||
filename = filename[2:]
|
||||
}
|
||||
filename = strings.TrimLeft(strings.Replace(filename, `\`, "/", -1), `/`)
|
||||
filename = filepath.ToSlash(filename)
|
||||
filename = filepath.Clean(filename)
|
||||
return filename
|
||||
}
|
||||
|
||||
func statFile(filename string) (info os.FileInfo, reader io.ReadCloser, err error) {
|
||||
info, err = os.Lstat(filename)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// content
|
||||
if info.Mode()&os.ModeSymlink != 0 {
|
||||
var target string
|
||||
target, err = os.Readlink(filename)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
reader = ioutil.NopCloser(bytes.NewBuffer([]byte(target)))
|
||||
} else if !info.IsDir() {
|
||||
reader, err = os.Open(filename)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
reader = ioutil.NopCloser(bytes.NewBuffer(nil))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (z *Zip) Add(relpath, abspath string) error {
|
||||
info, rdc, err := statFile(abspath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rdc.Close()
|
||||
|
||||
hdr, err := zip.FileInfoHeader(info)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hdr.Name = sanitizedName(relpath)
|
||||
if info.IsDir() {
|
||||
hdr.Name += "/"
|
||||
}
|
||||
hdr.Method = zip.Deflate // compress method
|
||||
writer, err := z.CreateHeader(hdr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = io.Copy(writer, rdc)
|
||||
return err
|
||||
}
|
||||
|
||||
func CompressToZip(w http.ResponseWriter, rootDir string) {
|
||||
rootDir = filepath.Clean(rootDir)
|
||||
zipFileName := filepath.Base(rootDir) + ".zip"
|
||||
|
||||
w.Header().Set("Content-Type", "application/zip")
|
||||
w.Header().Set("Content-Disposition", `attachment; filename="`+zipFileName+`"`)
|
||||
|
||||
zw := &Zip{Writer: zip.NewWriter(w)}
|
||||
defer zw.Close()
|
||||
|
||||
filepath.Walk(rootDir, func(path string, info os.FileInfo, err error) error {
|
||||
zipPath := path[len(rootDir):]
|
||||
return zw.Add(zipPath, path)
|
||||
})
|
||||
}
|
||||
|
||||
func ExtractFromZip(zipFile, path string, w io.Writer) (err error) {
|
||||
cf, err := zip.OpenReader(zipFile)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer cf.Close()
|
||||
|
||||
rd := ioutil.NopCloser(bytes.NewBufferString(path))
|
||||
patterns, err := dkignore.ReadIgnore(rd)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, file := range cf.File {
|
||||
matched, _ := dkignore.Matches(file.Name, patterns)
|
||||
if !matched {
|
||||
continue
|
||||
}
|
||||
rc, er := file.Open()
|
||||
if er != nil {
|
||||
err = er
|
||||
return
|
||||
}
|
||||
defer rc.Close()
|
||||
_, err = io.Copy(w, rc)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
return fmt.Errorf("File %s not found", strconv.Quote(path))
|
||||
}
|
||||
|
||||
15
zip_test.go
Normal file
15
zip_test.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestExtractFromZip(t *testing.T) {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
err := ExtractFromZip("testdata/test.zip", "*/foo.txt", buf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("Content: " + buf.String())
|
||||
}
|
||||
Reference in New Issue
Block a user