SVNからのミラー

This commit is contained in:
2026-01-21 03:59:11 +09:00
commit 8bc2fa7dfd
30 changed files with 1250 additions and 0 deletions

22
CHANGELOG.md Normal file
View File

@@ -0,0 +1,22 @@
# 1.3.0
* BSD2clause→ISCライセンスに変更
* 変なエンコーディングの変換
# 1.2.0
* 静的ファイルの修正
* 新しいルールに従い
* GNU MakeからBSD Makeに変更
* GPLv2→BSD2clouseライセンスに変更
# 1.1.0
* 言語はliblocale化
* 複数言語対応
* NetBSD対応
* IPアドレスを設定する様に
# 1.0.0
* PHPからGoに交換しました
* 今度からバージョンを付きます
# それ以前
* 色々

13
LICENSE.txt Normal file
View File

@@ -0,0 +1,13 @@
Copyright © 2018-2024 by 076.moe
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
SOFTWARE.

65
Makefile Normal file
View File

@@ -0,0 +1,65 @@
NAME!=cat common/common.go | grep "var sofname" | awk '{print $$4}' | sed "s/\"//g"
VERSION!=cat common/common.go | grep "var version" | awk '{print $$4}' | sed "s/\"//g"
PREFIX=/usr/local
MANPREFIX=${PREFIX}/man
CNFPREFIX?=/etc
CC=CGO_ENABLED=0 go build
RELEASE=-ldflags="-s -w" -buildvcs=false
all:
${CC} ${RELEASE} -o ${NAME}
release:
env GOOS=linux GOARCH=amd64 ${CC} ${RELEASE} -o bin/${NAME}-${VERSION}-linux-amd64
env GOOS=linux GOARCH=arm64 ${CC} ${RELEASE} -o bin/${NAME}-${VERSION}-linux-arm64
env GOOS=linux GOARCH=arm ${CC} ${RELEASE} -o bin/${NAME}-${VERSION}-linux-arm
env GOOS=linux GOARCH=riscv64 ${CC} ${RELEASE} -o bin/${NAME}-${VERSION}-linux-riscv64
env GOOS=linux GOARCH=386 ${CC} ${RELEASE} -o bin/${NAME}-${VERSION}-linux-386
env GOOS=linux GOARCH=ppc64 ${CC} ${RELEASE} -o bin/${NAME}-${VERSION}-linux-ppc64
env GOOS=linux GOARCH=mips64 ${CC} ${RELEASE} -o bin/${NAME}-${VERSION}-linux-mips64
env GOOS=openbsd GOARCH=amd64 ${CC} ${RELEASE} -o bin/${NAME}-${VERSION}-openbsd-amd64
env GOOS=openbsd GOARCH=386 ${CC} ${RELEASE} -o bin/${NAME}-${VERSION}-openbsd-386
env GOOS=openbsd GOARCH=arm64 ${CC} ${RELEASE} -o bin/${NAME}-${VERSION}-openbsd-arm64
env GOOS=openbsd GOARCH=arm ${CC} ${RELEASE} -o bin/${NAME}-${VERSION}-openbsd-arm
env GOOS=openbsd GOARCH=mips64 ${CC} ${RELEASE} -o bin/${NAME}-${VERSION}-openbsd-mips64
env GOOS=freebsd GOARCH=amd64 ${CC} ${RELEASE} -o bin/${NAME}-${VERSION}-freebsd-amd64
env GOOS=freebsd GOARCH=386 ${CC} ${RELEASE} -o bin/${NAME}-${VERSION}-freebsd-386
env GOOS=freebsd GOARCH=arm ${CC} ${RELEASE} -o bin/${NAME}-${VERSION}-freebsd-arm4
env GOOS=freebsd GOARCH=arm64 ${CC} ${RELEASE} -o bin/${NAME}-${VERSION}-freebsd-arm64
env GOOS=freebsd GOARCH=riscv64 ${CC} ${RELEASE} -o bin/${NAME}-${VERSION}-freebsd-riscv64
env GOOS=netbsd GOARCH=amd64 ${CC} ${RELEASE} -o bin/${NAME}-${VERSION}-netbsd-amd64
env GOOS=netbsd GOARCH=386 ${CC} ${RELEASE} -o bin/${NAME}-${VERSION}-netbsd-386
env GOOS=netbsd GOARCH=arm ${CC} ${RELEASE} -o bin/${NAME}-${VERSION}-netbsd-arm4
env GOOS=netbsd GOARCH=arm64 ${CC} ${RELEASE} -o bin/${NAME}-${VERSION}-netbsd-arm64
env GOOS=illumos GOARCH=amd64 ${CC} ${RELEASE} -o bin/${NAME}-${VERSION}-illumos-amd64
clean:
rm -f ${NAME} ${NAME}-${VERSION}.tar.gz
dist: clean
mkdir -p ${NAME}-${VERSION}
cp -R LICENSE.txt Makefile README.md CHANGELOG.md\
view static common src logo.jpg\
${NAME}.1 main.go *.json go.mod go.sum ${NAME}-${VERSION}
tar zcfv ${NAME}-${VERSION}.tar.gz ${NAME}-${VERSION}
rm -rf ${NAME}-${VERSION}
install:
mkdir -p ${DESTDIR}${PREFIX}/bin
cp -f ${NAME} ${DESTDIR}${PREFIX}/bin
chmod 755 ${DESTDIR}${PREFIX}/bin/${NAME}
mkdir -p ${DESTDIR}${MANPREFIX}/man1
sed "s/VERSION/${VERSION}/g" < ${NAME}.1 > ${DESTDIR}${MANPREFIX}/man1/${NAME}.1
chmod 644 ${DESTDIR}${MANPREFIX}/man1/${NAME}.1
mkdir -p ${DESTDIR}${PREFIX}/share/${NAME}/archive
chmod 755 ${DESTDIR}${PREFIX}/share/${NAME}/archive
mkdir -p ${DESTDIR}${CNFPREFIX}/${NAME}
chmod 755 ${DESTDIR}${CNFPREFIX}/${NAME}
uninstall:
rm -f ${DESTDIOR}${PREFIX}/bin/${NAME}\
${DESTDIR}${MANPREFIX}/man1/${NAME}.1\
${DESTDIR}${CNFPREFIX}/${NAME}\
${DESTDIR}${PREFIX}/share/${NAME}
.PHONY: all release clean dist install uninstall

31
README.md Normal file
View File

@@ -0,0 +1,31 @@
# 保存サイト
世界初FOSS系ウエブアーカイバーです。
# インストールする方法
## OpenBSD
```sh
cd hozonsite
make
doas make install
```
## FreeBSD
```sh
cd hozonsite
make
doas make install MANPREFIX=/usr/local/share/man
```
## NetBSD
```sh
cd hozonsite
make
doas make install PREFIX=/usr/pkg MANPREFIX=/usr/pkg/share/man
```
## Linux
```sh
cd hozonsite
bmake
doas bmake install PREFIX=/usr MANPREFIX=/usr/share/man
```

12
common/common.go Normal file
View File

@@ -0,0 +1,12 @@
package common
var sofname = "hozonsite"
var version = "1.3.0"
func GetSofname() string {
return sofname
}
func GetVersion() string {
return version
}

5
config.json Normal file
View File

@@ -0,0 +1,5 @@
{
"webpath": "/var/www/htdocs/hozonsite",
"domain": "https://hozon.site",
"ip": "0.0.0.0"
}

7
go.mod Normal file
View File

@@ -0,0 +1,7 @@
module gitler.moe/suwako/hozonsite
go 1.20
require gitler.moe/suwako/goliblocale v1.0.0
require golang.org/x/text v0.14.0 // indirect

4
go.sum Normal file
View File

@@ -0,0 +1,4 @@
gitler.moe/suwako/goliblocale v1.0.0 h1:QiQKNzdgpavwmAaYhAb5pth0I6qS8IJ7q2hYAgpXacU=
gitler.moe/suwako/goliblocale v1.0.0/go.mod h1:pdv9Go5taevY8ClBOA+oLXjGap7G1RmIVKUMF8HSJmU=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=

33
hozonsite.1 Normal file
View File

@@ -0,0 +1,33 @@
.TH 保存サイト 1 hozonsite\-VERSION
.SH ソフト名
保存サイト - 世界初FOSS系ウエブアーカイバー
.SH 概要
.B hozonsite
[\fI\,オプション\/\fR] [\fI\,ポート番号\/\fR]
.SH 説明
.PP
保存サイトは世界初FOSS系ウエブアーカイバーです。
.TP
\fB\-v\fR
バージョンを表示
.TP
\fB\-s [ポート番号]\fR
ポート番号でサーバーを開始デフォルト9920
.TP
\fB\-h\fR
ヘルプを表示
.TP
.B オプションなし
ローカルにウェブページを保存
.SH 会話
.PP
XMPP: xmpp:hozonsite@chat.xmpp.076.ne.jp?join
.br
IRC: irc.076.ne.jp/6697 #hozonsite
.br
メーリングリスト: (開発中)
.SH バグ報告
.PP
バグは下記のURLまでご報告下さい
.br
https://gitler.moe/suwako/hozonsite/issues

17
locale/en.json Normal file
View File

@@ -0,0 +1,17 @@
{
"hozonsite": "Hozon Site",
"desc": "It is the world's first FOSS web archiver.",
"logo": "Logo",
"top": "Top",
"langchange": "Language change",
"totop": "Return to toppage",
"tophe": "To toppage.",
"topwhatsave": "Which page will you archive?",
"hozon": "Archive",
"archwhozonsite": "Archived with Hozon Site.",
"areadyhozon": "Pages that already got archived:",
"willreallyhozon": "This page seems to have been already archived.<br />Do you really want to proceed?",
"yesreallyhozon": "Yes, please archive!!",
"errfuseiurl": "The URL should start with \"http://\" or \"https://\".",
"errfusei": "Unknown error."
}

17
locale/ja.json Normal file
View File

@@ -0,0 +1,17 @@
{
"hozonsite": "保存サイト",
"desc": "世界初FOSS系ウエブアーカイバーです。",
"logo": "ロゴ",
"top": "トップ",
"langchange": "言語変更",
"totop": "トップページに戻る",
"tophe": "トップページへ",
"topwhatsave": "どのページを保存しますか?",
"hozon": "保存",
"archwhozonsite": "保存サイトでアーカイブしました。",
"areadyhozon": "既に保存されたページ:",
"willreallyhozon": "このページが既に保存されているみたいです。<br />本当に手続きましょうか?",
"yesreallyhozon": "はい、保存して下さい!!",
"errfuseiurl": "URLは「http://」又は「https://」で始めます。",
"errfusei": "不正なエラー。"
}

BIN
logo.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 KiB

113
main.go Normal file
View File

@@ -0,0 +1,113 @@
package main
import (
"fmt"
"os"
"strings"
"strconv"
"gitler.moe/suwako/hozonsite/src"
"gitler.moe/suwako/hozonsite/common"
)
func help() {
fmt.Println("使い方:")
fmt.Println(
common.GetSofname() + " -v :バージョンを表示",
)
fmt.Println(
common.GetSofname() +
" -s [ポート番号] ポート番号でウェブサーバーを実行デフォルト9920",
)
fmt.Println(
common.GetSofname() +
" -h :ヘルプを表示",
)
fmt.Println(
common.GetSofname() +
" <URL> :コマンドラインでウェブサイトを保存",
)
}
func saveurlcmd(url string, cnf src.Config) {
// 結局HTTPかHTTPSじゃないわね…
if !src.Checkprefix(url) {
fmt.Println("URLは不正です。終了…")
return
}
// パラメートルの文字(?、=等)を削除
eurl := src.Stripurl(url)
// 既に/usr/local/share/hozonsite/archiveに存在するかどうか
exist := src.Checkexist(eurl, cnf.Datapath)
// 既に存在したら、使う
var confirm string
// あ、既に存在する
if len(exist) > 0 {
fmt.Println("このページが既に保存されているみたいです。")
fmt.Println("本当に手続きましょうか? [y/N]")
// 既に存在するページのURLを表示
for _, ex := range exist {
fmt.Println(strings.Replace(ex, cnf.Datapath, cnf.Domain, 1))
}
fmt.Scanf("%s", &confirm)
}
// 存在しない OR 「本当に手続きましょうか」でYを入力した場合
if len(exist) == 0 || confirm == "y" || confirm == "Y" {
path := src.Mkdirs(eurl, cnf.Datapath)
// ページをダウンロード
src.Getpage(url, path)
// 色々の必須な編集
src.Scanpage(path, eurl, cnf.Datapath)
// 新しいURLを表示
fmt.Println(cnf.Domain + strings.Replace(path, cnf.Datapath, "", 1))
}
}
func main() {
// コンフィグファイル
cnf, err := src.Getconf()
if err != nil {
fmt.Println(err)
return
}
// コマンドラインのパラメートル
args := os.Args
if len(args) == 2 {
// バージョンを表示
if args[1] == "-v" {
fmt.Println(common.GetSofname() + "-" + common.GetVersion())
return
} else if args[1] == "-s" { // :9920でウェブサーバーを実行
src.Serv(cnf, 9920)
} else if args[1] == "-h" { // ヘルプを表示
help()
return
} else {
// コマンドラインでウェブサイトを保存
saveurlcmd(args[1], cnf)
return
}
} else if len(args) == 3 && args[1] == "-s" {
// 好みなポート番号でウェブサーバーを実行
// でも、数字じゃないかもしん
if port, err := strconv.Atoi(args[2]); err != nil {
fmt.Printf("%qは数字ではありません。\n", args[2])
return
} else {
// OK、実行しよ〜
src.Serv(cnf, port)
}
} else {
// パラメートルは不明の場合、ヘルプを表示
help()
return
}
}

71
src/config.go Normal file
View File

@@ -0,0 +1,71 @@
package src
import (
"os"
"fmt"
"runtime"
"encoding/json"
"io/ioutil"
"errors"
)
type Config struct {
Configpath, Webpath, Datapath, Domain, IP string
}
var cnf Config
func Getconf () (Config, error) {
// バイナリ、データ、及びFreeBSDとNetBSDの場合、コンフィグ
prefix := "/usr"
// BSDだけはただの/usrではない
if runtime.GOOS == "freebsd" || runtime.GOOS == "openbsd" {
prefix += "/local"
} else if runtime.GOOS == "netbsd" {
prefix += "/pkg"
}
// コンフィグファイル
cnf.Configpath = "/etc/hozonsite/config.json"
cnf.Datapath = prefix + "/share/hozonsite"
// また、FreeBSDとNetBSDだけは違う場所だ。OpenBSDは正しい場所
// FreeBSD = /usr/local/etc/hozonsite/config.json
// NetBSD = /usr/pkg/etc/hozonsite/config.json
if runtime.GOOS == "freebsd" || runtime.GOOS == "netbsd" {
cnf.Configpath = prefix + cnf.Configpath
}
// コンフィグファイルがなければ、死ね
data, err := ioutil.ReadFile(cnf.Configpath)
if err != nil {
fmt.Println("confif.jsonを開けられません", err)
return cnf, errors.New(
"コンフィグファイルは " + cnf.Configpath + " に創作して下さい。",
)
}
var payload map[string]interface{}
json.Unmarshal(data, &payload)
if payload["webpath"] == nil {
return cnf, errors.New("「webpath」の値が設置していません。")
}
if payload["domain"] == nil {
return cnf, errors.New("「domain」の値が設置していません。")
}
if payload["ip"] == nil {
return cnf, errors.New("「ip」の値が設置していません。")
}
if _, err := os.Stat(payload["webpath"].(string)); err != nil {
fmt.Printf("%v\n", err)
return cnf, errors.New(
"mkdiorコマンドをつかって、 " + payload["webpath"].(string),
)
}
cnf.Webpath = payload["webpath"].(string) // データパス
cnf.Domain = payload["domain"].(string) // ドメイン名
cnf.IP = payload["ip"].(string) // IP
payload = nil // もういらなくなった
return cnf, nil
}

97
src/getpage.go Normal file
View File

@@ -0,0 +1,97 @@
package src
import (
"os"
"fmt"
"net/http"
"io"
"regexp"
"strings"
"golang.org/x/text/encoding/japanese"
"golang.org/x/text/transform"
)
// URLでパラメートル?、=等)がある場合
func Stripurl (url string) string {
res := strings.ReplaceAll(url, "?", "")
res = strings.ReplaceAll(res, "=", "")
return res
}
func Getpage (url string, path string) {
// ページを読み込む
curl, err := http.Get(url)
if err != nil {
fmt.Println("CURLエラー", err)
return
}
// ソフトの終了する時に実行する
defer curl.Body.Close()
// ページの内容を読み込む
body, err2 := io.ReadAll(curl.Body)
if err2 != nil {
fmt.Println("読込エラー:", err2)
return
}
// Content-TypeヘッダーはUTF-8又は駄目のエンコーディングかの確認
checkJis := `(?i)<meta.*?charset=(["']?)shift[_-]?jis`
jisRegex, errr := regexp.Compile(checkJis)
if errr != nil {
fmt.Println(errr)
return
}
checkEuc := `(?i)<meta.*?charset=(["']?)euc[_-]?jp`
eucRegex, erre := regexp.Compile(checkEuc)
if erre != nil {
fmt.Println(erre)
return
}
// 文字エンコーディングを変換する
if jisRegex.Match(body) {
shiftJISDecoder := japanese.ShiftJIS.NewDecoder()
utf8Reader := transform.NewReader(
strings.NewReader(string(body)),
shiftJISDecoder,
)
utf8Body, err3 := io.ReadAll(utf8Reader)
if err3 != nil {
fmt.Println("文字エンコーディング変換エラー:", err3)
return
}
body = utf8Body
} else if eucRegex.Match(body) {
eucJPDecoder := japanese.EUCJP.NewDecoder()
utf8Reader := transform.NewReader(
strings.NewReader(string(body)),
eucJPDecoder,
)
utf8Body, err3 := io.ReadAll(utf8Reader)
if err3 != nil {
fmt.Println("文字エンコーディング変換エラー:", err3)
return
}
body = utf8Body
}
// 空index.htmlファイルを創作する
fn, err4 := os.Create(path + "/index.html")
if err4 != nil {
fmt.Println("ファイルの創作エラー:", err4)
return
}
// ソフトの終了する時に実行する
defer fn.Close()
// あのindex.htmlファイルに内容をそのまま書き込む
_, err5 := fn.WriteString(string(body))
if err5 != nil {
fmt.Println("ファイル書込エラー:", err5)
}
}

15
src/mime.go Normal file
View File

@@ -0,0 +1,15 @@
package src
func getmime () map[string]string {
return map[string]string {
"text/css": ".css",
"text/javascript": ".js",
"image/png": ".png",
"image/jpeg": ".jpg",
"image/webp": ".webp",
"image/gif": ".gif",
"font/ttf": ".ttf",
"font/woff2": ".woff2",
"image/vnd.microsoft.icon": ".ico",
}
}

55
src/prep.go Normal file
View File

@@ -0,0 +1,55 @@
package src
import (
"os"
"time"
"fmt"
"strings"
"path/filepath"
)
// HTTPかHTTPSの確認
func Checkprefix (url string) bool {
return strings.HasPrefix(
url, "http://") || strings.HasPrefix(url, "https://",
)
}
// ページは既に存在するの?
func Checkexist (url string, prefix string) []string {
res, err := filepath.Glob(prefix + "/archive/*" + url2path(url))
if err != nil {
fmt.Println("Err:", err)
}
return res
}
// http:/かhttps:/はいらない。最後の「/」は必要
func url2path (url string) string {
res := ""
if strings.HasPrefix(url, "https:/") {
res = strings.Replace(url, "https:/", "", 1)
} else {
res = strings.Replace(url, "http:/", "", 1)
}
if strings.HasSuffix(res, "/") {
res = strings.TrimSuffix(res, "/")
}
return res
}
// 必要なフォルダの創作
func Mkdirs (url string, prefix string) string {
rep := url2path(url)
t := time.Now().Unix()
path := fmt.Sprint(prefix, "/archive/", t, rep)
err := os.MkdirAll(path, 0755)
if err != nil {
fmt.Println("失敗:", err)
}
return path
}

172
src/scanpage.go Normal file
View File

@@ -0,0 +1,172 @@
package src
import (
"os"
"fmt"
"strings"
"net/http"
"net/url"
"io"
"regexp"
"errors"
"path/filepath"
)
func Scanpage (path string, domain string, thisdomain string) error {
// 先に保存したページを読み込む
fn, err := os.ReadFile(path + "/index.html")
if err != nil { return err }
// 要らないタグを削除
var script = regexp.MustCompile(
`(<script.*</script>)`).ReplaceAllString(string(fn), "",
)
var noscript = regexp.MustCompile(
`(<noscript.*</noscript>)`).ReplaceAllString(string(script), "",
)
var audio = regexp.MustCompile(
`(<audio.*</audio>)`).ReplaceAllString(string(noscript), "",
)
var video = regexp.MustCompile(
`(<video.*</video>)`).ReplaceAllString(string(audio), "",
)
var iframe = regexp.MustCompile(
`(<iframe.*</iframe>)`).ReplaceAllString(string(video), "",
)
// 追加ダウンロード+ローカル化
var ass = regexp.MustCompile(
// ルールに違反けど、長いからしょうがない・・・
`(<img.*src=['"]|<meta.*content=['"]|<link.*href=['"])(.*\.)(png|webp|jpg|jpeg|gif|css|js|ico|svg|ttf|woff2)(\?[^'"]*)?`,
)
// 必要であれば、ページ内のURLを修正
spath := "static/"
if !strings.HasSuffix(path, "/") { spath = "/" + spath }
spath = path + spath
// また、追加ダウンロードのファイルに上記のフォルダを創作
err = os.Mkdir(spath, 0755)
if err != nil { return err }
repmap := make(map[string]string)
for _, cssx := range ass.FindAllString(iframe, -1) {
// ページ内のURLを受け取る
s := regexp.MustCompile(
`(.*src=['"]|.*content=['"]|.*href=['"])`).Split(cssx, -1,
)
ss := regexp.MustCompile(`(['"].*)`).Split(s[1], -1)
ogurl := ss[0] // 変わる前に元のURLを保存して
// URLは//で始まるは愛
if strings.HasPrefix(ss[0], "//") {
ss[0] = "https:" + ss[0]
}
// ファイル名を見つけて
fss := strings.Split(ss[0], "/")
assdom := ""
filename := fss[len(fss)-1]
// httpかhttpsで始まる場合
if strings.HasPrefix(ss[0], "http://") ||
strings.HasPrefix(ss[0], "https://") {
assdom = fss[2]
}
// フォルダの創作
asspath := path + "/static/" + assdom
err = os.MkdirAll(asspath, 0755)
// 出来なければ、死ね
if err != nil { return err }
// ファイル名がなければ、次に値にスキップしてね
if filename == "" { continue }
// httpかhttpsで始まったら、ダウンロードだけしよう
if strings.HasPrefix(ss[0], "http://") ||
strings.HasPrefix(ss[0], "https://") {
err = dlres(ss[0], filepath.Join(asspath, filename))
if err != nil { return err }
} else {
// ローカルファイルなら、ちょっと変更は必要となるかしら
u, err := url.Parse(domain)
if err != nil { return err }
rel, err := url.Parse(ss[0])
if err != nil { return err }
af := u.ResolveReference(rel).String()
err = dlres(af, filepath.Join(asspath, filename))
if err != nil { return err }
}
repmap[ogurl] = filepath.Join("/static", assdom, filename)
if assdom == "" {
repmap[ogurl] = filepath.Join("/static", filename)
}
if err != nil {
fmt.Println(err)
return errors.New("ダウンロードに失敗:")
}
}
// URLをローカル化
for ourl, lurl := range repmap {
aurl := strings.ReplaceAll(path, thisdomain, "") + stripver(lurl)
iframe = strings.ReplaceAll(iframe, ourl, aurl)
}
// index.htmlファイルを更新する
err = os.WriteFile(path + "/index.html", []byte(iframe), 0644)
if err != nil {
fmt.Println(err)
return errors.New("書込に失敗")
}
// エラーが出なかったから、返すのは不要
return nil
}
// 画像、JS、CSS等ファイルのURLでパラメートルがある場合
func stripver (durl string) string {
u, err := url.Parse(durl)
if err != nil {
fmt.Println("エラー:", err)
return ""
}
u.RawQuery = ""
return u.Path
}
func dlres (durl string, dest string) error {
// ダウンロード
res, err := http.Get(durl)
if err != nil { return err }
defer res.Body.Close()
// URLでパラメートルがあれば、消す
dest = stripver(dest)
// MIMEタイプを確認
ct := res.Header.Get("Content-Type")
for mime, ext := range getmime() {
if strings.Contains(ct, mime) && !strings.HasSuffix(dest, ext) {
dest += ext
break
}
}
// ファイルを作成
f, err := os.Create(dest)
if err != nil { return err }
defer f.Close()
// ファイルを書き込む
_, err = io.Copy(f, res.Body)
if err != nil { return err }
return nil
}

291
src/srv.go Normal file
View File

@@ -0,0 +1,291 @@
package src
import (
"text/template"
"fmt"
"net/http"
"strings"
"strconv"
"time"
"os"
"encoding/json"
"gitler.moe/suwako/hozonsite/common"
"gitler.moe/suwako/goliblocale"
)
type (
Page struct {
Err, Lan, Ver, Ves, Url, Body string
i18n map[string]string
Ext []Exist // 既に存在する場合
}
Stat struct { // APIのみ
Url, Ver string
}
Exist struct {
Date, Url string
}
)
var ftmpl []string
var data *Page
func (p Page) T (key string) string {
return p.i18n[key]
}
// 言語設定、デフォルトja
func initloc (r *http.Request) string {
supportedLanguages := map[string]bool{
"ja": true,
"en": true,
}
cookie, err := r.Cookie("lang")
if err != nil {
return "ja"
}
if _, ok := supportedLanguages[cookie.Value]; ok {
return cookie.Value
} else {
return "ja"
}
}
func tspath (p string) string {
pc := strings.Split(p, "/")
for i := len(pc) - 1; i >= 0; i-- {
if _, err := strconv.Atoi(pc[i]); err == nil {
return pc[i]
}
}
return ""
}
func handleStatic(path string, cnf Config, w http.ResponseWriter, r *http.Request) {
if !strings.HasSuffix(path, ".css") &&
!strings.HasSuffix(path, ".png") &&
!strings.HasSuffix(path, ".jpeg") &&
!strings.HasSuffix(path, ".jpg") &&
!strings.HasSuffix(path, ".webm") &&
!strings.HasSuffix(path, ".gif") &&
!strings.HasSuffix(path, ".js") {
http.NotFound(w, r)
return
}
fpath := cnf.Datapath + "/archive/" + path
http.ServeFile(w, r, fpath)
}
func handlePost(w http.ResponseWriter, r *http.Request, cnf Config) {
err := r.ParseForm()
if err != nil {
fmt.Println(err)
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
// 言語変更
if lang := r.PostFormValue("lang"); lang != "" {
http.SetCookie(
w,
&http.Cookie{Name: "lang", Value: lang, MaxAge: 31536000, Path: "/"},
)
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
var exist []string
langu := initloc(r)
i18n, err := goliblocale.GetLocale(cnf.Webpath + "/locale/" + langu)
if err != nil {
fmt.Printf("liblocaleエラー%v", err)
return
}
if r.PostForm.Get("hozonsite") == "" {
data.Err = i18n["errfusei"]
ftmpl[0] = cnf.Webpath + "/view/404.html"
return
}
url := r.PostForm.Get("hozonsite")
// HTTPかHTTPSじゃない場合
if !Checkprefix(url) {
data.Err = i18n["errfuseiurl"]
ftmpl[0] = cnf.Webpath + "/view/404.html"
return
}
eurl := Stripurl(url)
exist = Checkexist(eurl, cnf.Datapath)
if len(exist) == 0 || r.PostForm.Get("agree") == "1" {
path := Mkdirs(eurl, cnf.Datapath)
Getpage(url, path)
Scanpage(path, eurl, cnf.Datapath)
http.Redirect(
w,
r,
cnf.Domain + strings.Replace(path, cnf.Datapath, "", 1),
http.StatusSeeOther,
)
return
}
ftmpl[0] = cnf.Webpath + "/view/check.html"
data.Url = url
var existing []Exist
e := Exist{}
for _, ex := range exist {
ti, err := strconv.ParseInt(tspath(ex), 10, 64)
if err != nil {
fmt.Println(err)
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
t := time.Unix(ti, 0)
e.Date = t.Format("2006年01月02日 15:04:05")
e.Url = strings.Replace(ex, cnf.Datapath, cnf.Domain, 1)
existing = append(existing, e)
}
data.Ext = existing
}
// ホームページ
func siteHandler (cnf Config) func (http.ResponseWriter, *http.Request) {
return func (w http.ResponseWriter, r *http.Request) {
ftmpl = []string{
cnf.Webpath + "/view/index.html",
cnf.Webpath + "/view/header.html",
cnf.Webpath + "/view/footer.html",
}
version := common.GetVersion()
data = &Page{
Ver: version,
Ves: strings.ReplaceAll(version, ".", ""),
}
lang := initloc(r)
data.Lan = lang
i18n, err := goliblocale.GetLocale(cnf.Webpath + "/locale/" + lang)
if err != nil {
fmt.Printf("liblocaleエラー%v", err)
return
}
data.i18n = i18n
ftmpl[0] = cnf.Webpath + "/view/index.html"
tmpl := template.Must(template.ParseFiles(ftmpl[0], ftmpl[1], ftmpl[2]))
if r.Method == "POST" {
handlePost(w, r, cnf)
}
tmpl = template.Must(template.ParseFiles(ftmpl[0], ftmpl[1], ftmpl[2]))
tmpl.Execute(w, data)
}
}
// /api TODO
func apiHandler (cnf Config) func (http.ResponseWriter, *http.Request) {
return func (w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(200)
version := common.GetVersion()
buf, _ := json.MarshalIndent(
&Stat{Url: cnf.Domain, Ver: version},
"",
" ",
)
_, _ = w.Write(buf)
}
}
// /archive
func archiveHandler (cnf Config) func (http.ResponseWriter, *http.Request) {
return func (w http.ResponseWriter, r *http.Request) {
version := common.GetVersion()
ftmpl := []string{
cnf.Webpath + "/view/index.html",
cnf.Webpath + "/view/header.html",
cnf.Webpath + "/view/footer.html",
}
data := &Page{Ver: version, Ves: strings.ReplaceAll(version, ".", "")}
lang := initloc(r)
data.Lan = lang
i18n, err := goliblocale.GetLocale(cnf.Webpath + "/locale/" + lang)
if err != nil {
fmt.Printf("liblocaleエラー%v", err)
return
}
data.i18n = i18n
ftmpl[0] = cnf.Webpath + "/view/index.html"
tmpl := template.Must(template.ParseFiles(ftmpl[0], ftmpl[1], ftmpl[2]))
path := strings.TrimPrefix(r.URL.Path, "/archive/")
if strings.Contains(path, "/static/") {
handleStatic(path, cnf, w, r)
return
}
pth := r.URL.Path
if !strings.HasSuffix(pth, "/") &&
!strings.HasSuffix(pth, "index.html") {
pth += "/index.html"
} else if strings.HasSuffix(pth, "/") &&
!strings.HasSuffix(pth, "index.html") {
pth += "index.html"
}
file := cnf.Datapath + pth
if _, err := os.Stat(file); os.IsNotExist(err) {
http.Redirect(w, r, "/404", http.StatusSeeOther)
return
}
bdy, err := os.ReadFile(file)
if err != nil {
http.Redirect(w, r, "/404", http.StatusSeeOther)
return
}
data.Body = string(bdy)
tmpl = template.Must(
template.ParseFiles(cnf.Webpath + "/view/archive.html"),
)
tmpl.Execute(w, data)
data = nil
}
}
// サーバー
func Serv (cnf Config, port int) {
http.Handle(
"/static/",
http.StripPrefix("/static/",
http.FileServer(http.Dir(cnf.Webpath + "/static"))),
)
http.HandleFunc("/api/", apiHandler(cnf))
http.HandleFunc("/archive/", archiveHandler(cnf))
http.HandleFunc("/", siteHandler(cnf))
fmt.Println(fmt.Sprint(
"http://" + cnf.IP + ":",
port,
" でサーバーを実行中。終了するには、CTRL+Cを押して下さい。"),
)
http.ListenAndServe(fmt.Sprint(cnf.IP + ":", port), nil)
}

0
static/archive/.kara Normal file
View File

BIN
static/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
static/git.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

BIN
static/logo.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

95
static/style.css Normal file
View File

@@ -0,0 +1,95 @@
body {
background: #232629;
color: #fcfcfc;
}
.headerimg {
border: 1px solid #dd0000;
box-shadow: 0px 0px 11px 5px #ff0000;
}
.jswarning, .error {
color: rgb(252, 252, 252);
padding: 10px;
z-index: 5000;
position: fixed;
top: 0px;
left: 0px;
right: 0px;
}
.jswarning {
background-color: rgb(170, 170, 0);
display: none;
border: 4px #dde22d ridge;
}
.error {
background-color: rgb(170, 0, 0);
border: 4px #e22d2d ridge;
}
a {
color: #ffeb3b;
}
hr {
border-color: #dd0000;
box-shadow: 2px 2px 10px #ff0000;
width: 100%;
}
.body, input {
color: #ea8181;
border: 1px #ff3b3b groove;
}
.body {
margin: 0 auto;
max-width: 800px;
padding: 8px;
background-color: #320202;
box-shadow: 0px 0px 11px 5px #ff0000;
}
.central {
margin: 0 auto;
max-width: 700px;
}
input {
background-color: #600e0e;
font-size: 24px;
border-radius: 4px;
}
input[type="text"] {
width: 99%;
max-width: 700px;
border-color: #ea8181;
}
select {
background-color: #600e0e;
color: #ea8181;
font-size: 18px;
border-radius: 4px;
max-width: 700px;
border: 1px #ff3b3b groove;
padding: 8px;
width: 200px;
}
select, input.langchange {
height: 35px;
vertical-align: middle;
}
.submit, .footer, h1 {
margin-top: 32px;
text-align: center;
}
input.langchange {
font-size: 24px;
}

4
view/404.html Normal file
View File

@@ -0,0 +1,4 @@
{{template "header" .}}
{{ .Err }}<br />
<a href="/">{{.T "totop"}}</a>
{{template "footer" .}}

38
view/archive.html Normal file
View File

@@ -0,0 +1,38 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<style>
.archhead {
background-color: #320202 !important;
display: block !important;
border: 4px #ff3b3b groove !important;
color: #ea8181 !important;
padding: 10px !important;
z-index: 999999 !important;
position: fixed !important;
left: 0 !important;
top: 0 !important;
font-size: 18px !important;
font-family: unset !important;
width: 100% !important;
text-align: left !important;
}
.archbody {
padding-top: 74px !important;
}
.archlink {
color: #ffeb3b !important;
}
</style>
</head>
<body>
<div class="archhead">
{{.T "archwhozonsite"}} <a class="archlink" href="https://technicalsuwako.moe/blog/hozonsite-{{.Ves}}">hozonsite-{{ .Ver }}</a><br />
<a class="archlink" href="/">{{.T "tophe"}}</a>
</div>
<div class="archbody">
{{ .Body }}
</div>
</body>
</html>

20
view/check.html Normal file
View File

@@ -0,0 +1,20 @@
{{template "header" .}}
<h3>{{.Url}}</h3>
{{.T "areadyhozon"}}<br />
{{range $i, $e := .Ext}}
<a href="{{$e.Url}}">
{{$e.Date}}
</a>
<br />
{{end}}
<p>
{{.T "willreallyhozon"}}
</p>
<form action="/" method="post">
<input type="hidden" name="hozonsite" value="{{.Url}}" />
<input type="hidden" name="agree" value="1" />
<div class="submit">
<input type="submit" name="submit" value="{{.T "yesreallyhozon"}}" />
</div>
</form>
{{template "footer" .}}

12
view/footer.html Normal file
View File

@@ -0,0 +1,12 @@
{{define "footer"}}
</div>
</div>
<div class="footer">
<a href="https://gitler.moe/suwako/hozonsite"><img src="/static/git.png" alt="Git" /></a> |
<a href="https://076.moe/"></a>
<br />
<a href="https://technicalsuwako.moe/blog/hozonsite-{{.Ves}}">hozonsite-{{.Ver}}</a>
</div>
</body>
</html>
{{end}}

32
view/header.html Normal file
View File

@@ -0,0 +1,32 @@
{{define "header"}}
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja">
<head>
<meta content="text/html; charset=utf-8" http-equiv="content-type" />
<meta name="title" content="{{.T "hozonsite"}}" />
<meta name="description" content="{{.T "desc"}}" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{{.T "hozonsite"}}〜{{.T "top"}}</title>
<link rel="icon" type="image/x-icon" href="/static/favicon.ico" />
<link rel="stylesheet" type="text/css" href="/static/style.css" />
</head>
<body>
<h1>
<img class="headerimg" src="/static/logo.jpg" alt="{{.T "logo"}}" />
</h1>
<div class="body">
<p>
<a href="/">{{.T "top"}}</a>
</p>
<form method="post" action="/">
<div class="central">
<select class="langchange" name="lang">
<option value="ja"{{if eq .Lan "ja"}} selected{{end}}>日本語</option>
<option value="en"{{if eq .Lan "en"}} selected{{end}}>English</option>
</select>
<input class="langchange" type="submit" name="langchange" value="{{.T "langchange"}}" />
</div>
</form>
<hr />
<div class="central">
{{end}}

9
view/index.html Normal file
View File

@@ -0,0 +1,9 @@
{{template "header" .}}
{{.T "topwhatsave"}}
<form action="/" method="post">
<input type="text" name="hozonsite" value="" />
<div class="submit">
<input type="submit" name="submit" value="{{.T "hozon"}}" />
</div>
</form>
{{template "footer" .}}