mirror of
https://github.com/blacktop/ipsw.git
synced 2026-05-08 12:22:26 +00:00
feat: ipsw daemon (API) (#219)
This commit is contained in:
@@ -23,7 +23,6 @@ __debug_bin
|
||||
*.zip
|
||||
*.im4p
|
||||
*_Restore
|
||||
System
|
||||
usr
|
||||
*.xz
|
||||
*.bin
|
||||
|
||||
+253
-40
@@ -136,7 +136,7 @@ builds:
|
||||
env:
|
||||
- CGO_ENABLED=1
|
||||
- CC=zig cc -target x86_64-linux-musl
|
||||
- CC=zig c++ -target x86_64-linux-musl
|
||||
- CXX=zig c++ -target x86_64-linux-musl
|
||||
goos:
|
||||
- linux
|
||||
goarch:
|
||||
@@ -151,7 +151,7 @@ builds:
|
||||
env:
|
||||
- CGO_ENABLED=1
|
||||
- CC=zig cc -target aarch64-linux-musl
|
||||
- CC=zig c++ -target aarch64-linux-musl
|
||||
- CXX=zig c++ -target aarch64-linux-musl
|
||||
goos:
|
||||
- linux
|
||||
goarch:
|
||||
@@ -172,7 +172,7 @@ builds:
|
||||
# env:
|
||||
# - CGO_ENABLED=1
|
||||
# - CC=zig cc -target x86_64-linux-musl
|
||||
# - CC=zig c++ -target x86_64-linux-musl
|
||||
# - CXX=zig c++ -target x86_64-linux-musl
|
||||
# - CGO_LDFLAGS=-L/tmp/install_x86_64/usr/local/lib
|
||||
# - PKG_CONFIG_PATH=/tmp/install_x86_64/usr/local/lib/pkgconfig
|
||||
# - CGO_CFLAGS=-I/tmp/install_x86_64/usr/local/include/libusb-1.0 -I/tmp/install_x86_64/usr/local/include
|
||||
@@ -199,7 +199,7 @@ builds:
|
||||
# env:
|
||||
# - CGO_ENABLED=1
|
||||
# - CC=zig cc -target aarch64-linux-musl
|
||||
# - CC=zig c++ -target aarch64-linux-musl
|
||||
# - CXX=zig c++ -target aarch64-linux-musl
|
||||
# - CGO_LDFLAGS=-L/tmp/install_aarch64/usr/local/lib
|
||||
# - PKG_CONFIG_PATH=/tmp/install_aarch64/usr/local/lib/pkgconfig
|
||||
# - CGO_CFLAGS=-I/tmp/install_aarch64/usr/local/include/libusb-1.0 -I/tmp/install_aarch64/usr/local/include
|
||||
@@ -220,7 +220,7 @@ builds:
|
||||
env:
|
||||
- CGO_ENABLED=1
|
||||
- CC=zig cc -target x86_64-windows-gnu
|
||||
- CC=zig c++ -target x86_64-windows-gnu
|
||||
- CXX=zig c++ -target x86_64-windows-gnu
|
||||
goos:
|
||||
- windows
|
||||
goarch:
|
||||
@@ -235,7 +235,7 @@ builds:
|
||||
env:
|
||||
- CGO_ENABLED=1
|
||||
- CC=zig cc -target aarch64-windows-gnu
|
||||
- CC=zig c++ -target aarch64-windows-gnu
|
||||
- CXX=zig c++ -target aarch64-windows-gnu
|
||||
goos:
|
||||
- windows
|
||||
goarch:
|
||||
@@ -244,10 +244,87 @@ builds:
|
||||
flags:
|
||||
- -trimpath
|
||||
ldflags: -s -w -X github.com/blacktop/ipsw/cmd/ipsw/cmd.AppVersion={{.Version}} -X github.com/blacktop/ipsw/cmd/ipsw/cmd.AppBuildTime={{.Date}}
|
||||
# DAEMONS ###########################################################
|
||||
- id: darwin_daemon_build
|
||||
main: ./cmd/ipswd
|
||||
binary: ipswd
|
||||
env:
|
||||
- CGO_ENABLED=1
|
||||
goos:
|
||||
- darwin
|
||||
goarch:
|
||||
- amd64
|
||||
- arm64
|
||||
mod_timestamp: "{{ .CommitTimestamp }}"
|
||||
flags:
|
||||
- -trimpath
|
||||
ldflags: -s -w -X github.com/blacktop/ipsw/api/types.BuildVersion={{.Version}} -X github.com/blacktop/ipsw/api/types.BuildTime={{.Date}}
|
||||
- id: linux_amd64_daemon_build
|
||||
main: ./cmd/ipswd
|
||||
binary: ipswd
|
||||
# env:
|
||||
# - CGO_ENABLED=1
|
||||
# - CC=zig cc -target x86_64-linux-musl
|
||||
# - CXX=zig c++ -target x86_64-linux-musl
|
||||
goos:
|
||||
- linux
|
||||
goarch:
|
||||
- amd64
|
||||
mod_timestamp: "{{ .CommitTimestamp }}"
|
||||
flags:
|
||||
- -trimpath
|
||||
ldflags: -s -w -X github.com/blacktop/ipsw/api/types.BuildVersion={{.Version}} -X github.com/blacktop/ipsw/api/types.BuildTime={{.Date}}
|
||||
- id: linux_arm64_daemon_build
|
||||
main: ./cmd/ipswd
|
||||
binary: ipswd
|
||||
# env:
|
||||
# - CGO_ENABLED=1
|
||||
# - CC=zig cc -target aarch64-linux-musl
|
||||
# - CXX=zig c++ -target aarch64-linux-musl
|
||||
goos:
|
||||
- linux
|
||||
goarch:
|
||||
- arm64
|
||||
mod_timestamp: "{{ .CommitTimestamp }}"
|
||||
flags:
|
||||
- -trimpath
|
||||
ldflags: -s -w -X github.com/blacktop/ipsw/api/types.BuildVersion={{.Version}} -X github.com/blacktop/ipsw/api/types.BuildTime={{.Date}}
|
||||
- id: windows_amd64_daemon_build
|
||||
main: ./cmd/ipswd
|
||||
binary: ipswd
|
||||
env:
|
||||
- CGO_ENABLED=1
|
||||
- CC=zig cc -target x86_64-windows-gnu
|
||||
- CXX=zig c++ -target x86_64-windows-gnu
|
||||
goos:
|
||||
- windows
|
||||
goarch:
|
||||
- amd64
|
||||
mod_timestamp: "{{ .CommitTimestamp }}"
|
||||
flags:
|
||||
- -trimpath
|
||||
ldflags: -s -w -X github.com/blacktop/ipsw/api/types.BuildVersion={{.Version}} -X github.com/blacktop/ipsw/api/types.BuildTime={{.Date}}
|
||||
- id: windows_arm64_daemon_build
|
||||
main: ./cmd/ipswd
|
||||
binary: ipswd
|
||||
env:
|
||||
- CGO_ENABLED=1
|
||||
- CC=zig cc -target aarch64-windows-gnu
|
||||
- CXX=zig c++ -target aarch64-windows-gnu
|
||||
goos:
|
||||
- windows
|
||||
goarch:
|
||||
- arm64
|
||||
mod_timestamp: "{{ .CommitTimestamp }}"
|
||||
flags:
|
||||
- -trimpath
|
||||
ldflags: -s -w -X github.com/blacktop/ipsw/api/types.BuildVersion={{.Version}} -X github.com/blacktop/ipsw/api/types.BuildTime={{.Date}}
|
||||
|
||||
universal_binaries:
|
||||
- id: darwin_build
|
||||
replace: false
|
||||
- id: darwin_daemon_build
|
||||
replace: false
|
||||
|
||||
archives:
|
||||
- id: default_archive
|
||||
@@ -272,7 +349,7 @@ archives:
|
||||
files:
|
||||
- README.md
|
||||
- LICENSE
|
||||
- completions/*
|
||||
- completions/ipsw/*
|
||||
- manpages/*
|
||||
wrap_in_directory: true
|
||||
- id: extras_archive
|
||||
@@ -294,7 +371,7 @@ archives:
|
||||
files:
|
||||
- README.md
|
||||
- LICENSE
|
||||
- completions/*
|
||||
- completions/ipsw/*
|
||||
- manpages/*
|
||||
wrap_in_directory: true
|
||||
- id: frida_archive
|
||||
@@ -314,9 +391,31 @@ archives:
|
||||
files:
|
||||
- README.md
|
||||
- LICENSE
|
||||
- completions/*
|
||||
- completions/ipsw/*
|
||||
- manpages/*
|
||||
wrap_in_directory: true
|
||||
- id: daemon_archive
|
||||
builds:
|
||||
- darwin_daemon_build
|
||||
- linux_amd64_daemon_build
|
||||
- linux_arm64_daemon_build
|
||||
- windows_amd64_daemon_build
|
||||
- windows_arm64_daemon_build
|
||||
name_template: >-
|
||||
{{ .ProjectName }}_{{ .Version }}_
|
||||
{{- if eq .Os "darwin" }}macOS
|
||||
{{- else if eq .Os "ios" }}iOS
|
||||
{{- else }}{{ .Os }}{{ end }}_
|
||||
{{- if eq .Arch "amd64" }}x86_64
|
||||
{{- else if eq .Arch "386" }}i386
|
||||
{{- else if eq .Arch "all" }}universal
|
||||
{{- else }}{{ .Arch }}{{ end }}_daemon
|
||||
rlcp: true
|
||||
files:
|
||||
- README.md
|
||||
- LICENSE
|
||||
- completions/ipswd/*
|
||||
wrap_in_directory: true
|
||||
|
||||
brews:
|
||||
- tap:
|
||||
@@ -341,9 +440,9 @@ brews:
|
||||
- ipsw-frida
|
||||
install: |
|
||||
bin.install "ipsw"
|
||||
bash_completion.install "completions/_bash" => "ipsw"
|
||||
zsh_completion.install "completions/_zsh" => "_ipsw"
|
||||
fish_completion.install "completions/_fish" => "ipsw.fish"
|
||||
bash_completion.install "completions/ipsw/_bash" => "ipsw"
|
||||
zsh_completion.install "completions/ipsw/_zsh" => "_ipsw"
|
||||
fish_completion.install "completions/ipsw/_fish" => "ipsw.fish"
|
||||
man1.install Dir["manpages/*"]
|
||||
test: |
|
||||
system "#{bin}/ipsw --version"
|
||||
@@ -366,12 +465,37 @@ brews:
|
||||
- ipsw
|
||||
install: |
|
||||
bin.install "ipsw"
|
||||
bash_completion.install "completions/_bash" => "ipsw"
|
||||
zsh_completion.install "completions/_zsh" => "_ipsw"
|
||||
fish_completion.install "completions/_fish" => "ipsw.fish"
|
||||
bash_completion.install "completions/ipsw/_bash" => "ipsw"
|
||||
zsh_completion.install "completions/ipsw/_zsh" => "_ipsw"
|
||||
fish_completion.install "completions/ipsw/_fish" => "ipsw.fish"
|
||||
man1.install Dir["manpages/*"]
|
||||
test: |
|
||||
system "#{bin}/ipsw --version"
|
||||
- name: ipswd
|
||||
tap:
|
||||
owner: blacktop
|
||||
name: homebrew-tap
|
||||
ids:
|
||||
- daemon_archive
|
||||
folder: Formula
|
||||
homepage: "https://github.com/blacktop/ipsw"
|
||||
description: "ipsw - Daemon."
|
||||
license: MIT
|
||||
dependencies:
|
||||
- name: ipsw
|
||||
- name: libusb
|
||||
type: optional
|
||||
install: |
|
||||
bin.install "ipswd"
|
||||
bash_completion.install "completions/ipswd/_bash" => "ipswd"
|
||||
zsh_completion.install "completions/ipswd/_zsh" => "_ipswd"
|
||||
fish_completion.install "completions/ipswd/_fish" => "ipswd.fish"
|
||||
service: |
|
||||
run: ipswd
|
||||
keep_alive: true
|
||||
sockets: /var/run/ipsw.sock
|
||||
test: |
|
||||
system "#{bin}/ipswd --version"
|
||||
|
||||
checksum:
|
||||
name_template: "checksums.txt"
|
||||
@@ -401,9 +525,9 @@ aurs:
|
||||
mkdir -p "${pkgdir}/usr/share/bash-completion/completions/"
|
||||
mkdir -p "${pkgdir}/usr/share/zsh/site-functions/"
|
||||
mkdir -p "${pkgdir}/usr/share/fish/vendor_completions.d/"
|
||||
install -Dm644 "./completions/ipsw" "${pkgdir}/usr/share/bash-completion/completions/ipsw"
|
||||
install -Dm644 "./completions/_ipsw" "${pkgdir}/usr/share/zsh/site-functions/_ipsw"
|
||||
install -Dm644 "./completions/ipsw.fish" "${pkgdir}/usr/share/fish/vendor_completions.d/ipsw.fish"
|
||||
install -Dm644 "./completions/ipsw/_bash" "${pkgdir}/usr/share/bash-completion/completions/ipsw"
|
||||
install -Dm644 "./completions/ipsw/_zsh" "${pkgdir}/usr/share/zsh/site-functions/_ipsw"
|
||||
install -Dm644 "./completions/ipsw/_fish" "${pkgdir}/usr/share/fish/vendor_completions.d/ipsw.fish"
|
||||
|
||||
# man pages
|
||||
install -Dm644 "./manpages/ipsw.1.gz" "${pkgdir}/usr/share/man/man1/ipsw.1.gz"
|
||||
@@ -422,16 +546,16 @@ nfpms:
|
||||
bindir: /usr/bin
|
||||
section: utils
|
||||
contents:
|
||||
- src: ./completions/_bash
|
||||
- src: ./completions/ipsw/_bash
|
||||
dst: /usr/share/bash-completion/completions/ipsw
|
||||
file_info:
|
||||
mode: 0644
|
||||
- src: ./completions/_fish
|
||||
dst: /usr/share/fish/vendor_completions.d/ipsw.fish
|
||||
- src: ./completions/ipsw/_zsh
|
||||
dst: /usr/share/zsh/vendor-completions/_ipsw
|
||||
file_info:
|
||||
mode: 0644
|
||||
- src: ./completions/_zsh
|
||||
dst: /usr/share/zsh/vendor-completions/_ipsw
|
||||
- src: ./completions/ipsw/_fish
|
||||
dst: /usr/share/fish/vendor_completions.d/ipsw.fish
|
||||
file_info:
|
||||
mode: 0644
|
||||
- src: ./manpages/ipsw.1.gz
|
||||
@@ -457,6 +581,61 @@ nfpms:
|
||||
lintian_overrides:
|
||||
- statically-linked-binary
|
||||
- changelog-file-missing-in-native-package
|
||||
- id: daemon
|
||||
builds:
|
||||
- linux_amd64_daemon_build
|
||||
- linux_arm64_daemon_build
|
||||
homepage: https://github.com/blacktop/ipsw
|
||||
description: |-
|
||||
ipsw daemon.
|
||||
maintainer: Blacktop <https://github.com/blacktop>
|
||||
license: MIT
|
||||
vendor: Blacktop
|
||||
bindir: /usr/bin
|
||||
section: utils
|
||||
contents:
|
||||
- src: ./hack/goreleaser/linux_install/ipsw.service
|
||||
dst: /lib/systemd/system/ipsws.service
|
||||
file_info:
|
||||
mode: 0644
|
||||
- src: ./hack/goreleaser/linux_install/ipsw.socket
|
||||
dst: /lib/systemd/system/ipsws.socket
|
||||
file_info:
|
||||
mode: 0644
|
||||
- src: ./completions/ipswd/_bash
|
||||
dst: /usr/share/bash-completion/completions/ipswd
|
||||
file_info:
|
||||
mode: 0644
|
||||
- src: ./completions/ipswd/_zsh
|
||||
dst: /usr/share/zsh/vendor-completions/_ipswd
|
||||
file_info:
|
||||
mode: 0644
|
||||
- src: ./completions/ipswd/_fish
|
||||
dst: /usr/share/fish/vendor_completions.d/ipswd.fish
|
||||
file_info:
|
||||
mode: 0644
|
||||
- src: ./LICENSE
|
||||
dst: /usr/share/doc/ipsw/copyright
|
||||
file_info:
|
||||
mode: 0644
|
||||
scripts:
|
||||
postinstall: "./hack/goreleaser/linux_install/postinstall.sh"
|
||||
postremove: "./hack/goreleaser/linux_install/postremove.sh"
|
||||
file_name_template: >-
|
||||
{{ .ProjectName }}_{{ .Version }}_
|
||||
{{- .Os }}_
|
||||
{{- if eq .Arch "amd64" }}x86_64
|
||||
{{- else if eq .Arch "386" }}i386
|
||||
{{- else }}{{ .Arch }}{{ end }}_daemon
|
||||
formats:
|
||||
- deb
|
||||
- rpm
|
||||
- apk
|
||||
- archlinux
|
||||
deb:
|
||||
lintian_overrides:
|
||||
- statically-linked-binary
|
||||
- changelog-file-missing-in-native-package
|
||||
# - id: packages_extras
|
||||
# file_name_template: "{{ .PackageName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}_extras"
|
||||
# builds:
|
||||
@@ -471,18 +650,18 @@ nfpms:
|
||||
# bindir: /usr/bin
|
||||
# section: utils
|
||||
# contents:
|
||||
# - src: ./completions/_bash
|
||||
# dst: /usr/share/bash-completion/completions/ipsw
|
||||
# file_info:
|
||||
# mode: 0644
|
||||
# - src: ./completions/_fish
|
||||
# dst: /usr/share/fish/completions/ipsw.fish
|
||||
# file_info:
|
||||
# mode: 0644
|
||||
# - src: ./completions/_zsh
|
||||
# dst: /usr/share/zsh/vendor-completions/_ipsw
|
||||
# file_info:
|
||||
# mode: 0644
|
||||
# - src: ./completions/ipsw/_bash
|
||||
# dst: /usr/share/bash-completion/completions/ipsw
|
||||
# file_info:
|
||||
# mode: 0644
|
||||
# - src: ./completions/ipsw/_zsh
|
||||
# dst: /usr/share/zsh/vendor-completions/_ipsw
|
||||
# file_info:
|
||||
# mode: 0644
|
||||
# - src: ./completions/ipsw/_fish
|
||||
# dst: /usr/share/fish/vendor_completions.d/ipsw.fish
|
||||
# file_info:
|
||||
# mode: 0644
|
||||
# - src: ./manpages/ipsw.1.gz
|
||||
# dst: /usr/share/man/man1/ipsw.1.gz
|
||||
# file_info:
|
||||
@@ -517,15 +696,15 @@ snapcrafts:
|
||||
publish: true
|
||||
license: MIT
|
||||
extra_files:
|
||||
- source: ./completions/_bash
|
||||
- source: ./completions/ipsw/_bash
|
||||
destination: /usr/share/bash-completion/completions/ipsw
|
||||
mode: 0644
|
||||
- source: ./completions/_fish
|
||||
destination: /usr/share/fish/vendor_completions.d/ipsw.fish
|
||||
mode: 0644
|
||||
- source: ./completions/_zsh
|
||||
- source: ./completions/ipsw/_zsh
|
||||
destination: /usr/share/zsh/vendor-completions/_ipsw
|
||||
mode: 0644
|
||||
- source: ./completions/ipsw/_fish
|
||||
destination: /usr/share/fish/vendor_completions.d/ipsw.fish
|
||||
mode: 0644
|
||||
- source: ./manpages/ipsw.1.gz
|
||||
destination: /usr/share/man/man1/ipsw.1.gz
|
||||
mode: 0644
|
||||
@@ -536,6 +715,40 @@ snapcrafts:
|
||||
ipsw:
|
||||
command: ipsw
|
||||
plugs: ["home", "network"]
|
||||
- id: snaps-daemon
|
||||
builds:
|
||||
- linux_amd64_daemon_build
|
||||
- linux_arm64_daemon_build
|
||||
summary: ipsw - Daemon.
|
||||
description: |
|
||||
ipsw - Daemon.
|
||||
grade: stable
|
||||
confinement: strict
|
||||
publish: true
|
||||
license: MIT
|
||||
extra_files:
|
||||
- source: ./completions/ipsw/_bash
|
||||
destination: /usr/share/bash-completion/completions/ipsw
|
||||
mode: 0644
|
||||
- source: ./completions/ipsw/_zsh
|
||||
destination: /usr/share/zsh/vendor-completions/_ipsw
|
||||
mode: 0644
|
||||
- source: ./completions/ipsw/_fish
|
||||
destination: /usr/share/fish/vendor_completions.d/ipsw.fish
|
||||
mode: 0644
|
||||
- source: ./LICENSE
|
||||
destination: /usr/share/doc/ipsw/copyright
|
||||
mode: 0644
|
||||
apps:
|
||||
ipsw:
|
||||
command: ipswd
|
||||
daemon: simple
|
||||
sockets:
|
||||
sock:
|
||||
listen-stream: $SNAP_COMMON/ipsw.sock
|
||||
socket-group: socket-group
|
||||
socket-mode: 416
|
||||
plugs: ["home", "network", "network-bind"]
|
||||
|
||||
scoop:
|
||||
bucket:
|
||||
|
||||
@@ -8,7 +8,7 @@ GO_BIN=go
|
||||
.PHONY: build-deps
|
||||
build-deps: ## Install the build dependencies
|
||||
@echo " > Installing build deps"
|
||||
brew install $(GO_BIN) goreleaser zig unicorn libusb
|
||||
brew install $(GO_BIN) goreleaser zig unicorn libusb go-swagger/go-swagger/go-swagger
|
||||
|
||||
.PHONY: dev-deps
|
||||
dev-deps: ## Install the dev dependencies
|
||||
@@ -71,6 +71,14 @@ build-ios: ## Build ipsw for iOS
|
||||
@CGO_ENABLED=1 GOOS=ios GOARHC=arm64 CC=$(shell go env GOROOT)/misc/ios/clangwrap.sh $(GO_BIN) build -ldflags "-s -w -X github.com/blacktop/ipsw/cmd/ipsw/cmd.AppVersion=$(CUR_VERSION) -X github.com/blacktop/ipsw/cmd/ipsw/cmd.AppBuildTime==$(date -u +%Y%m%d)" ./cmd/ipsw
|
||||
@codesign --entitlements hack/make/data/ent.plist -s - -f ipsw
|
||||
|
||||
build-linux: ## Build ipsw (linux)
|
||||
@echo " > Building ipsw (linux)"
|
||||
@$(GO_BIN) mod download
|
||||
@CGO_ENABLED=1 GOOS=linux GOARHC=arm64 CC='zig cc -target aarch64-linux-musl' CXX='zig c++ -target aarch64-linux-musl' $(GO_BIN) build -ldflags "-s -w -X github.com/blacktop/ipsw/cmd/ipsw/cmd.AppVersion=$(CUR_VERSION) -X github.com/blacktop/ipsw/cmd/ipsw/cmd.AppBuildTime=$(date -u +%Y%m%d)" ./cmd/ipsw
|
||||
@echo " > Building ipswd (linux)"
|
||||
@CGO_ENABLED=0 GOOS=linux GOARHC=arm64 $(GO_BIN) build -ldflags "-s -w --X github.com/blacktop/ipsw/api/types.BuildVersion=$(CUR_VERSION) -X github.com/blacktop/ipsw/api/types.BuildTime=$(date -u +%Y%m%d)" ./cmd/ipswd
|
||||
|
||||
|
||||
.PHONY: docs
|
||||
docs: ## Build the cli docs
|
||||
@echo " > Updating CLI Docs"
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
# ipsw API
|
||||
|
||||
## TODO
|
||||
|
||||
<https://github.com/gin-gonic/examples>
|
||||
|
||||
- [ ] https://github.com/gin-gonic/examples/tree/master/send_chunked_data
|
||||
- [ ] https://github.com/gin-gonic/examples/tree/master/server-sent-event *(daemon status)*
|
||||
- [ ] https://github.com/gin-gonic/examples/tree/master/grpc/example1 *(daemon speed?)*
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// Package api contains common constants for daemon and client.
|
||||
package api
|
||||
|
||||
//go:generate swagger generate spec -o swagger.json
|
||||
|
||||
// Common constants for daemon and client.
|
||||
const (
|
||||
// DefaultVersion of Current REST API
|
||||
DefaultVersion = "1"
|
||||
)
|
||||
@@ -0,0 +1,51 @@
|
||||
// Package daemon provides the daemon routes
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"runtime"
|
||||
|
||||
"github.com/blacktop/ipsw/api"
|
||||
"github.com/blacktop/ipsw/api/types"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// AddRoutes adds the download routes to the router
|
||||
func AddRoutes(rg *gin.RouterGroup) {
|
||||
// swagger:route HEAD /_ping Daemon headDaemonPing
|
||||
//
|
||||
// Ping
|
||||
//
|
||||
// This will return if 200 the daemon is running.
|
||||
rg.HEAD("/_ping", pingHandler)
|
||||
// swagger:route GET /_ping Daemon getDaemonPing
|
||||
//
|
||||
// Ping
|
||||
//
|
||||
// This will return "OK" if the daemon is running.
|
||||
rg.GET("/_ping", pingHandler)
|
||||
// swagger:route GET /version Daemon getDaemonVersion
|
||||
//
|
||||
// Version
|
||||
//
|
||||
// This will return the daemon version info.
|
||||
rg.GET("/version", func(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, types.Version{
|
||||
ApiVersion: api.DefaultVersion,
|
||||
OSType: runtime.GOOS,
|
||||
BuilderVersion: types.BuildVersion,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func pingHandler(c *gin.Context) {
|
||||
c.Header("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||
c.Header("Pragma", "no-cache")
|
||||
|
||||
if c.Request.Method == "HEAD" {
|
||||
c.Header("Content-Type", "text/plain; charset=utf-8")
|
||||
c.Header("Content-Length", "0")
|
||||
return
|
||||
}
|
||||
c.String(http.StatusOK, "OK")
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// Package devicelist contains the /device_list routes for the API
|
||||
package devicelist
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"sort"
|
||||
|
||||
"github.com/blacktop/ipsw/pkg/xcode"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// AddRoutes adds the download routes to the router
|
||||
func AddRoutes(rg *gin.RouterGroup) {
|
||||
// swagger:route GET /device_list DeviceList getDeviceList
|
||||
//
|
||||
// List XCode Devices.
|
||||
//
|
||||
// This will return JSON of all XCode devices.
|
||||
//
|
||||
// Produces:
|
||||
// - application/json
|
||||
rg.GET("/device_list", func(c *gin.Context) {
|
||||
c.Header("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||
c.Header("Pragma", "no-cache")
|
||||
devices, err := xcode.GetDevices()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
sort.Sort(xcode.ByProductType{Devices: devices})
|
||||
|
||||
c.JSON(http.StatusOK, devices)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package download
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/blacktop/ipsw/internal/commands/download/ipsw"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func downloadIPSW(c *gin.Context) {
|
||||
version := c.Query("version")
|
||||
build := c.Query("build")
|
||||
device := c.Query("device")
|
||||
|
||||
c.IndentedJSON(http.StatusOK, gin.H{"version": version, "build": build, "device": device})
|
||||
}
|
||||
|
||||
func downloadLatestIPSWs(c *gin.Context) {
|
||||
c.IndentedJSON(http.StatusOK, gin.H{"count": 0})
|
||||
}
|
||||
|
||||
func latestVersion(c *gin.Context) {
|
||||
version, err := ipsw.GetLatestIosVersion("", false)
|
||||
if err != nil {
|
||||
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
|
||||
return
|
||||
}
|
||||
c.IndentedJSON(http.StatusOK, gin.H{"version": version})
|
||||
}
|
||||
|
||||
func latestBuild(c *gin.Context) {
|
||||
build, err := ipsw.GetLatestIosBuild()
|
||||
if err != nil {
|
||||
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
|
||||
return
|
||||
}
|
||||
c.IndentedJSON(http.StatusOK, gin.H{"build": build})
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// Package download contains the /download routes
|
||||
package download
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// AddRoutes adds the download routes to the router
|
||||
func AddRoutes(rg *gin.RouterGroup) {
|
||||
dl := rg.Group("/download")
|
||||
|
||||
// dl.GET("/dev", handler) // TODO:
|
||||
// dl.GET("/git", handler) // TODO:
|
||||
// dl.GET("/ipa", handler) // TODO:
|
||||
// dl.GET("/ipsw", downloadIPSW) // TODO:
|
||||
// dl.GET("/ipsw/ios/latest", downloadLatestIPSWs) // TODO:
|
||||
|
||||
// swagger:route GET /download/ipsw/ios/latest/version Download getDownloadLatestIPSWsVersion
|
||||
//
|
||||
// Latest iOS Version
|
||||
//
|
||||
// Get latest iOS version.
|
||||
dl.GET("/ipsw/ios/latest/version", latestVersion)
|
||||
// swagger:route GET /download/ipsw/ios/latest/build Download getDownloadLatestIPSWsBuild
|
||||
//
|
||||
// Latest iOS Build
|
||||
//
|
||||
// Get latest iOS build.
|
||||
dl.GET("/ipsw/ios/latest/build", latestBuild)
|
||||
|
||||
// dl.GET("/macos", handler) // TODO:
|
||||
// dl.GET("/ota", handler) // TODO:
|
||||
// dl.GET("/rss", handler) // TODO:
|
||||
// dl.GET("/tss", handler) // TODO:
|
||||
// dl.GET("/wiki", handler) // TODO:
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
// Package dsc provides the /dsc route and handlers
|
||||
package dsc
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
cmd "github.com/blacktop/ipsw/internal/commands/dsc"
|
||||
"github.com/blacktop/ipsw/pkg/dyld"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func dscImports(c *gin.Context) {
|
||||
dscPath := c.Query("path")
|
||||
if dscPath == "" {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "missing required 'path' query parameter"})
|
||||
return
|
||||
}
|
||||
f, err := dyld.Open(dscPath)
|
||||
if err != nil {
|
||||
c.AbortWithError(http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
imps, err := cmd.GetDylibsThatImport(f, c.Query("dylib"))
|
||||
if err != nil {
|
||||
c.AbortWithError(http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.IndentedJSON(http.StatusOK, gin.H{"path": dscPath, "imported_by": imps})
|
||||
}
|
||||
|
||||
func dscInfo(c *gin.Context) {
|
||||
dscPath := c.Query("path")
|
||||
if dscPath == "" {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "missing required 'path' query parameter"})
|
||||
return
|
||||
}
|
||||
f, err := dyld.Open(dscPath)
|
||||
if err != nil {
|
||||
c.AbortWithError(http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
info, err := cmd.GetInfo(f)
|
||||
if err != nil {
|
||||
c.AbortWithError(http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.IndentedJSON(http.StatusOK, gin.H{"path": dscPath, "info": info})
|
||||
}
|
||||
|
||||
func dscMacho(c *gin.Context) {
|
||||
dscPath := c.Query("path")
|
||||
f, err := dyld.Open(dscPath)
|
||||
if err != nil {
|
||||
c.AbortWithError(http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
image, err := f.Image(c.Query("dylib"))
|
||||
if err != nil {
|
||||
c.AbortWithError(http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
m, err := image.GetMacho()
|
||||
if err != nil {
|
||||
c.AbortWithError(http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.IndentedJSON(http.StatusOK, gin.H{"path": dscPath, "macho": m})
|
||||
}
|
||||
|
||||
func dscStrings(c *gin.Context) {
|
||||
dscPath := c.Query("path")
|
||||
f, err := dyld.Open(dscPath)
|
||||
if err != nil {
|
||||
c.AbortWithError(http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
pattern := c.Query("pattern")
|
||||
strs, err := cmd.GetStrings(f, pattern)
|
||||
if err != nil {
|
||||
c.AbortWithError(http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.IndentedJSON(http.StatusOK, gin.H{"path": dscPath, "strings": strs})
|
||||
}
|
||||
|
||||
func dscSymbols(c *gin.Context) {
|
||||
dscPath := c.Query("path")
|
||||
f, err := dyld.Open(dscPath)
|
||||
if err != nil {
|
||||
c.AbortWithError(http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
var lookups []cmd.Symbol
|
||||
if err := c.ShouldBindJSON(&lookups); err != nil {
|
||||
c.AbortWithError(http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
syms, err := cmd.GetSymbols(f, lookups)
|
||||
if err != nil {
|
||||
c.AbortWithError(http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.IndentedJSON(http.StatusOK, gin.H{"path": dscPath, "symbols": syms})
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
package dsc
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// AddRoutes adds the download routes to the router
|
||||
func AddRoutes(rg *gin.RouterGroup) {
|
||||
dr := rg.Group("/dsc")
|
||||
|
||||
// dr.GET("/a2f", handler) // TODO: implement this
|
||||
// dr.GET("/a2o", handler) // TODO: implement this
|
||||
// dr.GET("/a2s", handler) // TODO: implement this
|
||||
// dr.GET("/disass", handler) // TODO: implement this
|
||||
// dr.GET("/dump", handler) // TODO: implement this
|
||||
// dr.GET("/extract", handler) // TODO: implement this
|
||||
// dr.GET("/ida", handler) // TODO: implement this
|
||||
// dr.GET("/image", handler) // TODO: implement this
|
||||
|
||||
// swagger:route GET /dsc/imports DSC getDscImports
|
||||
//
|
||||
// Imports
|
||||
//
|
||||
// Get list of dylibs that import a given dylib.
|
||||
//
|
||||
// Produces:
|
||||
// - application/json
|
||||
//
|
||||
// Parameters:
|
||||
// + name: path
|
||||
// in: query
|
||||
// description: path to IPSW
|
||||
// required: true
|
||||
// type: string
|
||||
// + name: dylib
|
||||
// in: query
|
||||
// description: dylib to search for
|
||||
// required: true
|
||||
// type: string
|
||||
dr.GET("/imports", dscImports)
|
||||
// swagger:route GET /dsc/info DSC getDscInfo
|
||||
//
|
||||
// Info
|
||||
//
|
||||
// Get info about a given DSC
|
||||
//
|
||||
// Produces:
|
||||
// - application/json
|
||||
//
|
||||
// Parameters:
|
||||
// + name: path
|
||||
// in: query
|
||||
// description: path to IPSW
|
||||
// required: true
|
||||
// type: string
|
||||
dr.GET("/info", dscInfo)
|
||||
// swagger:route GET /dsc/macho DSC getDscMacho
|
||||
//
|
||||
// MachO
|
||||
//
|
||||
// Get MachO info for a given dylib in the DSC.
|
||||
//
|
||||
// Produces:
|
||||
// - application/json
|
||||
//
|
||||
// Parameters:
|
||||
// + name: path
|
||||
// in: query
|
||||
// description: path to IPSW
|
||||
// required: true
|
||||
// type: string
|
||||
// + name: dylib
|
||||
// in: query
|
||||
// description: dylib to search for
|
||||
// required: true
|
||||
// type: string
|
||||
dr.GET("/macho", dscMacho)
|
||||
// dr.GET("/o2a", handler) // TODO: implement this
|
||||
// dr.GET("/objc", handler) // TODO: implement this
|
||||
// dr.GET("/patches", handler) // TODO: implement this
|
||||
// dr.GET("/search", handler) // TODO: implement this
|
||||
// dr.GET("/slide", handler) // TODO: implement this
|
||||
// dr.GET("/split", handler) // TODO: implement this
|
||||
|
||||
// swagger:route GET /dsc/str DSC getDscStrings
|
||||
//
|
||||
// Strings
|
||||
//
|
||||
// Get strings in the DSC that match a given pattern.
|
||||
//
|
||||
// Produces:
|
||||
// - application/json
|
||||
//
|
||||
// Parameters:
|
||||
// + name: path
|
||||
// in: query
|
||||
// description: path to IPSW
|
||||
// required: true
|
||||
// type: string
|
||||
// + name: pattern
|
||||
// in: query
|
||||
// description: regex to search for
|
||||
// required: true
|
||||
// type: string
|
||||
dr.GET("/str", dscStrings)
|
||||
// dr.GET("/stubs", handler) // TODO: implement this
|
||||
// dr.GET("/swift", handler) // TODO: implement this
|
||||
|
||||
// swagger:route GET /dsc/symaddr DSC getDscSymbols
|
||||
//
|
||||
// Symbols
|
||||
//
|
||||
// Get symbols addresses in the DSC that match a given lookup JSON payload.
|
||||
//
|
||||
// Consumes:
|
||||
// - application/json
|
||||
//
|
||||
// Produces:
|
||||
// - application/json
|
||||
//
|
||||
// Parameters:
|
||||
// + name: path
|
||||
// in: query
|
||||
// description: path to IPSW
|
||||
// required: true
|
||||
// type: string
|
||||
dr.POST("/symaddr", dscSymbols)
|
||||
// dr.GET("/tbd", handler) // TODO: implement this
|
||||
// dr.GET("/webkit", handler) // TODO: implement this
|
||||
// dr.GET("/xref", handler) // TODO: implement this
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
package dtree
|
||||
@@ -0,0 +1,85 @@
|
||||
package extract
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/blacktop/ipsw/internal/commands/extract"
|
||||
cmd "github.com/blacktop/ipsw/internal/commands/extract"
|
||||
"github.com/blacktop/ipsw/internal/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func extractDSC(c *gin.Context) {
|
||||
var query extract.Config
|
||||
if err := c.BindQuery(&query); err != nil {
|
||||
c.IndentedJSON(http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
artifacts, err := cmd.DSC(&query)
|
||||
if err != nil {
|
||||
c.AbortWithError(http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
c.IndentedJSON(http.StatusOK, gin.H{"query": query, "artifacts": artifacts})
|
||||
}
|
||||
|
||||
func extractDMG(c *gin.Context) {
|
||||
var query cmd.Config
|
||||
if err := c.BindQuery(&query); err != nil {
|
||||
c.IndentedJSON(http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
if !utils.StrSliceHas([]string{"app", "sys", "fs"}, query.DmgType) {
|
||||
c.IndentedJSON(http.StatusBadRequest, fmt.Errorf("invalid dmg type: %s", query.DmgType))
|
||||
return
|
||||
}
|
||||
artifacts, err := cmd.DMG(&query)
|
||||
if err != nil {
|
||||
c.AbortWithError(http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
c.IndentedJSON(http.StatusOK, gin.H{"query": query, "artifacts": artifacts})
|
||||
}
|
||||
|
||||
func extractKBAG(c *gin.Context) {
|
||||
var query cmd.Config
|
||||
if err := c.BindQuery(&query); err != nil {
|
||||
c.IndentedJSON(http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
artifacts, err := cmd.Keybags(&query)
|
||||
if err != nil {
|
||||
c.AbortWithError(http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
c.IndentedJSON(http.StatusOK, gin.H{"query": query, "artifacts": artifacts})
|
||||
}
|
||||
|
||||
func extractKernel(c *gin.Context) {
|
||||
var query cmd.Config
|
||||
if err := c.BindQuery(&query); err != nil {
|
||||
c.IndentedJSON(http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
artifacts, err := cmd.Kernelcache(&query)
|
||||
if err != nil {
|
||||
c.AbortWithError(http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
c.IndentedJSON(http.StatusOK, gin.H{"query": query, "artifacts": artifacts})
|
||||
}
|
||||
|
||||
func extractPattern(c *gin.Context) {
|
||||
var query cmd.Config
|
||||
if err := c.BindQuery(&query); err != nil {
|
||||
c.IndentedJSON(http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
artifacts, err := cmd.Search(&query)
|
||||
if err != nil {
|
||||
c.AbortWithError(http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
c.IndentedJSON(http.StatusOK, gin.H{"query": query, "artifacts": artifacts})
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// Package extract provides the /extract API route
|
||||
package extract
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// AddRoutes adds the download routes to the router
|
||||
func AddRoutes(rg *gin.RouterGroup) {
|
||||
dl := rg.Group("/extract")
|
||||
|
||||
// swagger:route GET /extract/dsc Extract getExtractDsc
|
||||
//
|
||||
// DSC
|
||||
//
|
||||
// Extract dyld_shared_caches from an IPSW.
|
||||
dl.GET("/dsc", extractDSC)
|
||||
// swagger:route GET /extract/dmg Extract getExtractDmg
|
||||
//
|
||||
// DMG
|
||||
//
|
||||
// Extract DMGs from an IPSW.
|
||||
dl.GET("/dmg", extractDMG)
|
||||
// swagger:route GET /extract/kbag Extract getExtractKbags
|
||||
//
|
||||
// KBAG
|
||||
//
|
||||
// Extract KBAGs from an IPSW.
|
||||
dl.GET("/kbag", extractKBAG)
|
||||
// swagger:route GET /extract/kernel Extract getExtractKernel
|
||||
//
|
||||
// Kernel
|
||||
//
|
||||
// Extract kernelcaches from an IPSW.
|
||||
dl.GET("/kernel", extractKernel)
|
||||
// swagger:route GET /extract/pattern Extract getExtractPattern
|
||||
//
|
||||
// Pattern
|
||||
//
|
||||
// Extract files from an IPSW that match a given pattern.
|
||||
dl.GET("/pattern", extractPattern)
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// Package idev provides the /idev route and handlers
|
||||
package idev
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/blacktop/ipsw/pkg/usb"
|
||||
"github.com/blacktop/ipsw/pkg/usb/lockdownd"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func idevInfo(c *gin.Context) {
|
||||
conn, err := usb.NewConn()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, fmt.Errorf("failed to connect to usbmuxd: %w", err))
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
devices, err := conn.ListDevices()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(devices) == 0 {
|
||||
c.JSON(http.StatusOK, gin.H{"status": "no devices found", "devices": nil})
|
||||
return
|
||||
}
|
||||
|
||||
var dds []*lockdownd.DeviceValues
|
||||
|
||||
for _, device := range devices {
|
||||
cli, err := lockdownd.NewClient(device.SerialNumber)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
values, err := cli.GetValues()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
dds = append(dds, values)
|
||||
|
||||
cli.Close()
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"devices": dds})
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package idev
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// AddRoutes adds the download routes to the router
|
||||
func AddRoutes(rg *gin.RouterGroup) {
|
||||
ig := rg.Group("/idev")
|
||||
|
||||
// ig.GET("/afc", handler) // TODO: implement this
|
||||
// ig.GET("/afc/cat", handler) // TODO:
|
||||
// ig.GET("/afc/ls", handler) // TODO:
|
||||
// ig.GET("/afc/mkdir", handler) // TODO:
|
||||
// ig.GET("/afc/pull", handler) // TODO:
|
||||
// ig.GET("/afc/push", handler) // TODO:
|
||||
// ig.GET("/afc/rm", handler) // TODO:
|
||||
// ig.GET("/afc/tree", handler) // TODO:
|
||||
|
||||
// ig.GET("/apps", handler) // TODO: implement this
|
||||
// ig.GET("/apps/install", handler) // TODO: implement this
|
||||
// ig.GET("/apps/ls", handler) // TODO: implement this
|
||||
// ig.GET("/apps/uninstall", handler) // TODO: implement this
|
||||
|
||||
// ig.GET("/comp", handler) // TODO: implement this
|
||||
|
||||
// ig.GET("/crash", handler) // TODO: implement this
|
||||
// ig.GET("/crash/clear", handler) // TODO: implement this
|
||||
// ig.GET("/crash/ls", handler) // TODO: implement this
|
||||
// ig.GET("/crash/pull", handler) // TODO: implement this
|
||||
|
||||
// ig.GET("/diag", handler) // TODO: implement this
|
||||
// ig.GET("/diag/bat", handler) // TODO:
|
||||
// ig.GET("/diag/info", handler) // TODO:
|
||||
// ig.GET("/diag/ioreg", handler) // TODO:
|
||||
// ig.GET("/diag/mg", handler) // TODO:
|
||||
// ig.GET("/diag/restart", handler) // TODO:
|
||||
// ig.GET("/diag/shutdown", handler) // TODO:
|
||||
// ig.GET("/diag/sleep", handler) // TODO:
|
||||
|
||||
// ig.GET("/fsyms", handler) // TODO: implement this
|
||||
|
||||
// ig.GET("/img", handler) // TODO: implement this
|
||||
// ig.GET("/img/lookup", handler) // TODO: implement this
|
||||
// ig.GET("/img/ls", handler) // TODO: implement this
|
||||
// ig.GET("/img/mount", handler) // TODO: implement this
|
||||
// ig.GET("/img/unmount", handler) // TODO: implement this
|
||||
|
||||
// swagger:route GET /idev/info USB getIdevInfo
|
||||
//
|
||||
// Info
|
||||
//
|
||||
// Get info about USB connected devices.
|
||||
ig.GET("/info", idevInfo) // `ipsw idev list`
|
||||
|
||||
// ig.GET("/loc", handler) // TODO: implement this
|
||||
// ig.GET("/loc/clear", handler) // TODO: implement this
|
||||
// ig.GET("/loc/play", handler) // TODO: implement this
|
||||
// ig.GET("/loc/set", handler) // TODO: implement this
|
||||
|
||||
// ig.GET("/noti", handler) // TODO: implement this
|
||||
|
||||
// ig.GET("/pcap", handler) // TODO: implement this
|
||||
|
||||
// ig.GET("/prof", handler) // TODO: implement this
|
||||
// ig.GET("/prof/cloud", handler) // TODO: implement this
|
||||
// ig.GET("/prof/install", handler) // TODO: implement this
|
||||
// ig.GET("/prof/ls", handler) // TODO: implement this
|
||||
// ig.GET("/prof/rm", handler) // TODO: implement this
|
||||
// ig.GET("/prof/wifi", handler) // TODO: implement this
|
||||
|
||||
// ig.GET("/prov", handler) // TODO: implement this
|
||||
// ig.GET("/prov/clear", handler) // TODO: implement this
|
||||
// ig.GET("/prov/dump", handler) // TODO: implement this
|
||||
// ig.GET("/prov/install", handler) // TODO: implement this
|
||||
// ig.GET("/prov/ls", handler) // TODO: implement this
|
||||
// ig.GET("/prov/rm", handler) // TODO: implement this
|
||||
|
||||
// ig.GET("/proxy", handler) // TODO: implement this
|
||||
|
||||
// ig.GET("/ps", handler) // TODO: implement this
|
||||
|
||||
// ig.GET("/restore", handler) // TODO: implement this
|
||||
// ig.GET("/restore/enter", handler) // TODO: implement this
|
||||
// ig.GET("/restore/exit", handler) // TODO: implement this
|
||||
|
||||
// ig.GET("/screen", handler) // TODO: implement this
|
||||
|
||||
// ig.GET("/springb", handler) // TODO: implement this
|
||||
// ig.GET("/springb/icon", handler) // TODO: implement this
|
||||
// ig.GET("/springb/orient", handler) // TODO: implement this
|
||||
// ig.GET("/springb/wallpaper", handler) // TODO: implement this
|
||||
|
||||
// ig.GET("/syslog", handler) // TODO: implement this
|
||||
|
||||
// ig.GET("/wifi", handler) // TODO: implement this
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
package img4
|
||||
@@ -0,0 +1,39 @@
|
||||
// Package info provides a route for getting info about an IPSW or OTA file
|
||||
package info
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/blacktop/ipsw/internal/download"
|
||||
"github.com/blacktop/ipsw/pkg/info"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func getInfo(c *gin.Context) {
|
||||
path := c.Query("path")
|
||||
i, err := info.Parse(path)
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.IndentedJSON(http.StatusOK, gin.H{"path": path, "info": i})
|
||||
}
|
||||
|
||||
func getRemoteInfo(c *gin.Context) {
|
||||
insecure, _ := strconv.ParseBool(c.Query("insecure"))
|
||||
zr, err := download.NewRemoteZipReader(c.Query("url"), &download.RemoteConfig{
|
||||
Proxy: c.Query("proxy"),
|
||||
Insecure: insecure,
|
||||
})
|
||||
if err != nil {
|
||||
c.AbortWithError(http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
i, err := info.ParseZipFiles(zr.File)
|
||||
if err != nil {
|
||||
c.AbortWithError(http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
c.IndentedJSON(http.StatusOK, gin.H{"url": c.Query("url"), "info": i})
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package info
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// AddRoutes adds the download routes to the router
|
||||
func AddRoutes(rg *gin.RouterGroup) {
|
||||
ig := rg.Group("/info")
|
||||
// swagger:route GET /info/ipsw Info getIpswInfo
|
||||
//
|
||||
// IPSW
|
||||
//
|
||||
// Get IPSW info.
|
||||
//
|
||||
// Produces:
|
||||
// - application/json
|
||||
//
|
||||
// Parameters:
|
||||
// + name: path
|
||||
// in: query
|
||||
// description: path to IPSW
|
||||
// required: true
|
||||
// type: string
|
||||
ig.GET("/ipsw", getInfo)
|
||||
// swagger:route GET /info/ota Info getOtaInfo
|
||||
//
|
||||
// OTA
|
||||
//
|
||||
// Get OTA info.
|
||||
//
|
||||
// Produces:
|
||||
// - application/json
|
||||
//
|
||||
// Parameters:
|
||||
// + name: path
|
||||
// in: query
|
||||
// description: path to OTA
|
||||
// required: true
|
||||
// type: string
|
||||
ig.GET("/ota", getInfo)
|
||||
// swagger:route GET /info/ipsw/remote Info getRemoteIpswInfo
|
||||
//
|
||||
// Remote IPSW
|
||||
//
|
||||
// Get remote IPSW info.
|
||||
//
|
||||
// Produces:
|
||||
// - application/json
|
||||
//
|
||||
// Parameters:
|
||||
// + name: url
|
||||
// in: query
|
||||
// description: url to IPSW
|
||||
// required: true
|
||||
// type: string
|
||||
// + name: proxy
|
||||
// in: query
|
||||
// description: http proxy to use
|
||||
// type: string
|
||||
// + name: insecure
|
||||
// in: query
|
||||
// description: ignore TLS errors
|
||||
// type: boolean
|
||||
ig.GET("/ipsw/remote", getRemoteInfo)
|
||||
// swagger:route GET /info/ota/remote Info getRemoteOtaInfo
|
||||
//
|
||||
// Remote OTA
|
||||
//
|
||||
// Get remote OTA info.
|
||||
//
|
||||
// Produces:
|
||||
// - application/json
|
||||
//
|
||||
// Parameters:
|
||||
// + name: url
|
||||
// in: query
|
||||
// description: url to OTA
|
||||
// required: true
|
||||
// type: string
|
||||
// + name: proxy
|
||||
// in: query
|
||||
// description: http proxy to use
|
||||
// type: string
|
||||
// + name: insecure
|
||||
// in: query
|
||||
// description: ignore TLS errors
|
||||
// type: boolean
|
||||
ig.GET("/ota/remote", getRemoteInfo)
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
package ipsw
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/apex/log"
|
||||
"github.com/blacktop/go-plist"
|
||||
"github.com/blacktop/ipsw/internal/commands/ent"
|
||||
"github.com/blacktop/ipsw/internal/utils"
|
||||
"github.com/blacktop/ipsw/pkg/info"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type File struct {
|
||||
Name string
|
||||
Size int64
|
||||
Mode string
|
||||
ModTime time.Time
|
||||
}
|
||||
|
||||
func getFsFiles(c *gin.Context) {
|
||||
ipswPath := c.Query("path")
|
||||
ipswPath = filepath.Clean(ipswPath)
|
||||
|
||||
i, err := info.Parse(ipswPath)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
dmgPath, err := i.GetFileSystemOsDmg()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
if _, err := os.Stat(dmgPath); os.IsNotExist(err) {
|
||||
// extract filesystem DMG
|
||||
dmgs, err := utils.Unzip(ipswPath, "", func(f *zip.File) bool {
|
||||
return strings.EqualFold(filepath.Base(f.Name), dmgPath)
|
||||
})
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, fmt.Errorf("failed to extract %s from IPSW: %v", dmgPath, err))
|
||||
}
|
||||
if len(dmgs) == 0 {
|
||||
c.JSON(http.StatusInternalServerError, fmt.Errorf("failed to find %s in IPSW", dmgPath))
|
||||
}
|
||||
defer os.Remove(filepath.Clean(dmgs[0]))
|
||||
} else {
|
||||
utils.Indent(log.Debug, 2)(fmt.Sprintf("Found extracted %s", dmgPath))
|
||||
}
|
||||
|
||||
// mount filesystem DMG
|
||||
utils.Indent(log.Info, 2)(fmt.Sprintf("Mounting %s", dmgPath))
|
||||
mountPoint, alreadyMounted, err := utils.MountFS(dmgPath)
|
||||
if err != nil {
|
||||
if !errors.Is(err, utils.ErrMountResourceBusy) {
|
||||
c.JSON(http.StatusInternalServerError, fmt.Errorf("failed to mount DMG: %v", err))
|
||||
}
|
||||
}
|
||||
if alreadyMounted {
|
||||
utils.Indent(log.Info, 3)(fmt.Sprintf("%s already mounted", dmgPath))
|
||||
} else {
|
||||
defer func() {
|
||||
utils.Indent(log.Info, 2)(fmt.Sprintf("Unmounting %s", dmgPath))
|
||||
if err := utils.Retry(3, 2*time.Second, func() error {
|
||||
return utils.Unmount(mountPoint, false)
|
||||
}); err != nil {
|
||||
log.Errorf("failed to unmount %s at %s: %v", dmgPath, mountPoint, err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
var files []File
|
||||
if err := filepath.Walk(mountPoint, func(path string, info fs.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("prevent panic by handling failure accessing a path %q: %v", path, err)
|
||||
}
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
// return filepath.SkipDir
|
||||
}
|
||||
fpath, err := filepath.Rel(mountPoint, path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get relative path for %s: %v", path, err)
|
||||
}
|
||||
files = append(files, File{
|
||||
Name: fpath,
|
||||
Size: info.Size(),
|
||||
Mode: info.Mode().String(),
|
||||
ModTime: info.ModTime(),
|
||||
})
|
||||
return nil
|
||||
}); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.IndentedJSON(http.StatusOK, gin.H{"path": ipswPath, "files": files})
|
||||
}
|
||||
|
||||
func getFsEntitlements(c *gin.Context) {
|
||||
ipswPath := c.Query("path")
|
||||
ipswPath = filepath.Clean(ipswPath)
|
||||
|
||||
ents, err := ent.GetDatabase(ipswPath, "")
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
entDB := make(map[string]map[string]any)
|
||||
|
||||
for f, ent := range ents {
|
||||
ents := make(map[string]any)
|
||||
if err := plist.NewDecoder(bytes.NewReader([]byte(ent))).Decode(&ents); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, fmt.Errorf("failed to decode entitlements plist for %s: %v", f, err))
|
||||
}
|
||||
entDB[f] = ents
|
||||
}
|
||||
|
||||
c.IndentedJSON(http.StatusOK, gin.H{"path": ipswPath, "entitlements": entDB})
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package ipsw
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// AddRoutes adds the download routes to the router
|
||||
func AddRoutes(rg *gin.RouterGroup) {
|
||||
dl := rg.Group("/ipsw")
|
||||
// swagger:route GET /ipsw/fs/files IPSW getIpswFsFiles
|
||||
//
|
||||
// Files
|
||||
//
|
||||
// Get IPSW Filesystem DMG file listing.
|
||||
//
|
||||
// Produces:
|
||||
// - application/json
|
||||
//
|
||||
// Parameters:
|
||||
// + name: path
|
||||
// in: query
|
||||
// description: path to IPSW
|
||||
// required: true
|
||||
// type: string
|
||||
dl.GET("/fs/files", getFsFiles)
|
||||
// swagger:route GET /ipsw/fs/ents IPSW getIpswFsEntitlements
|
||||
//
|
||||
// Entitlements
|
||||
//
|
||||
// Get IPSW Filesystem DMG MachO entitlements.
|
||||
//
|
||||
// Produces:
|
||||
// - application/json
|
||||
//
|
||||
// Parameters:
|
||||
// + name: path
|
||||
// in: query
|
||||
// description: path to IPSW
|
||||
// required: true
|
||||
// type: string
|
||||
dl.GET("/fs/ents", getFsEntitlements)
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package kernel
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/blacktop/go-macho"
|
||||
"github.com/blacktop/ipsw/pkg/kernelcache"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func listKexts(c *gin.Context) {
|
||||
kernelPath := c.Query("path")
|
||||
|
||||
m, err := macho.Open(kernelPath)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
defer m.Close()
|
||||
|
||||
bundles, err := kernelcache.GetKexts(m)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"path": kernelPath, "kexts": bundles})
|
||||
}
|
||||
|
||||
func getSyscalls(c *gin.Context) {
|
||||
kernelPath := c.Query("path")
|
||||
m, err := macho.Open(kernelPath)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
defer m.Close()
|
||||
syscalls, err := kernelcache.GetSyscallTable(m)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"path": kernelPath, "syscalls": syscalls})
|
||||
}
|
||||
|
||||
func getVersion(c *gin.Context) {
|
||||
kernelPath := c.Query("path")
|
||||
m, err := macho.Open(kernelPath)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
defer m.Close()
|
||||
v, err := kernelcache.GetVersion(m)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"path": kernelPath, "version": v})
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
// Package kernel provides the /kernel routes
|
||||
package kernel
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// AddRoutes adds the download routes to the router
|
||||
func AddRoutes(rg *gin.RouterGroup) {
|
||||
kg := rg.Group("/kernel")
|
||||
// kg.GET("/ctfdump", handler) // TODO: implement this
|
||||
// kg.GET("/dec", handler) // TODO: implement this
|
||||
// kg.GET("/dwarf", handler) // TODO: implement this
|
||||
// kg.GET("/extract", handler) // TODO: implement this
|
||||
|
||||
// swagger:route GET /kernel/kexts Kernel getKernelKexts
|
||||
//
|
||||
// Kexts
|
||||
//
|
||||
// Get kernelcache KEXTs info.
|
||||
//
|
||||
// Produces:
|
||||
// - application/json
|
||||
//
|
||||
// Parameters:
|
||||
// + name: path
|
||||
// in: query
|
||||
// description: path to kernelcache
|
||||
// required: true
|
||||
// type: string
|
||||
kg.GET("/kexts", listKexts)
|
||||
// kg.GET("/sbopts", handler) // TODO: implement this
|
||||
// kg.GET("/symbolsets", handler) // TODO: implement this
|
||||
|
||||
// swagger:route GET /kernel/syscall Kernel getKernelSyscalls
|
||||
//
|
||||
// Syscalls
|
||||
//
|
||||
// Get kernelcache syscalls info.
|
||||
//
|
||||
// Produces:
|
||||
// - application/json
|
||||
//
|
||||
// Parameters:
|
||||
// + name: path
|
||||
// in: query
|
||||
// description: path to kernelcache
|
||||
// required: true
|
||||
// type: string
|
||||
kg.GET("/syscall", getSyscalls)
|
||||
// swagger:route GET /kernel/version Kernel getKernelVersion
|
||||
//
|
||||
// Version
|
||||
//
|
||||
// Get kernelcache version.
|
||||
//
|
||||
// Produces:
|
||||
// - application/json
|
||||
//
|
||||
// Parameters:
|
||||
// + name: path
|
||||
// in: query
|
||||
// description: path to kernelcache
|
||||
// required: true
|
||||
// type: string
|
||||
kg.GET("/version", getVersion)
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package macho
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/blacktop/go-macho"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// Info is the struct for the macho info route parameters
|
||||
type Info struct {
|
||||
Path string `form:"path" json:"path" binding:"required"`
|
||||
Arch string `form:"arch" json:"arch"`
|
||||
}
|
||||
|
||||
func machoInfo(c *gin.Context) {
|
||||
var m *macho.File
|
||||
var params Info
|
||||
|
||||
if err := c.BindQuery(¶ms); err != nil {
|
||||
c.IndentedJSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
fat, err := macho.OpenFat(params.Path)
|
||||
if err != nil {
|
||||
if err == macho.ErrNotFat { // not a fat binary
|
||||
m, err = macho.Open(params.Path)
|
||||
if err != nil {
|
||||
c.IndentedJSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
} else {
|
||||
c.IndentedJSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
} else { // fat binary
|
||||
if params.Arch == "" {
|
||||
c.IndentedJSON(http.StatusBadRequest, gin.H{"error": "'arch' query parameter is required for universal binaries"})
|
||||
return
|
||||
}
|
||||
for _, farch := range fat.Arches {
|
||||
if strings.EqualFold(farch.SubCPU.String(farch.CPU), params.Arch) {
|
||||
m = farch.File
|
||||
}
|
||||
}
|
||||
}
|
||||
c.IndentedJSON(http.StatusOK, gin.H{"info": m})
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package macho
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// AddRoutes adds the download routes to the router
|
||||
func AddRoutes(rg *gin.RouterGroup) {
|
||||
m := rg.Group("/macho")
|
||||
// m.GET("/a2o", handler) // TODO: implement this
|
||||
// m.GET("/a2s", handler) // TODO: implement this
|
||||
// m.GET("/bbl", handler) // TODO: implement this
|
||||
// m.GET("/disass", handler) // TODO: implement this
|
||||
// m.GET("/dump", handler) // TODO: implement this
|
||||
|
||||
// swagger:route GET /macho/info MachO getMachoInfo
|
||||
//
|
||||
// Info
|
||||
//
|
||||
// Get MachO info.
|
||||
//
|
||||
// Produces:
|
||||
// - application/json
|
||||
//
|
||||
// Parameters:
|
||||
// + name: path
|
||||
// in: query
|
||||
// description: path to MachO
|
||||
// required: true
|
||||
// type: string
|
||||
// + name: arch
|
||||
// in: query
|
||||
// description: architecture to get info for in universal MachO
|
||||
// required: false
|
||||
// type: string
|
||||
m.GET("/info", machoInfo)
|
||||
// m.GET("/lipo", handler) // TODO: implement this
|
||||
// m.GET("/o2a", handler) // TODO: implement this
|
||||
// m.GET("/patch", handler) // TODO: implement this
|
||||
// m.GET("/search", handler) // TODO: implement this
|
||||
// m.GET("/sign", handler) // TODO: implement this
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
package mdevs
|
||||
@@ -0,0 +1,74 @@
|
||||
// Package mount provides the /mount API route
|
||||
package mount
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/blacktop/ipsw/internal/commands/mount"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// AddRoutes adds the download routes to the router
|
||||
func AddRoutes(rg *gin.RouterGroup) {
|
||||
// swagger:route POST /mount/{type} Mount postMount
|
||||
//
|
||||
// Mount
|
||||
//
|
||||
// Mount a DMG inside a given IPSW.
|
||||
//
|
||||
// Produces:
|
||||
// - application/json
|
||||
//
|
||||
// Parameters:
|
||||
// + name: type
|
||||
// in: path
|
||||
// description: type of DMG to mount
|
||||
// pattern: (app|sys|fs)
|
||||
// required: true
|
||||
// type: string
|
||||
// + name: path
|
||||
// in: query
|
||||
// description: path to IPSW
|
||||
// required: true
|
||||
// type: string
|
||||
rg.POST("/mount/:type", func(c *gin.Context) {
|
||||
ipswPath := filepath.Clean(c.Query("path"))
|
||||
ctx, err := mount.DmgInIPSW(ipswPath, c.Param("type"))
|
||||
if err != nil {
|
||||
c.AbortWithError(http.StatusInternalServerError, err)
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"dmg_path": ctx.DmgPath, "mount_point": ctx.MountPoint, "already_mounted": ctx.AlreadyMounted})
|
||||
})
|
||||
// swagger:route POST /unmount Mount postUnmount
|
||||
//
|
||||
// Unmount
|
||||
//
|
||||
// Unmount a previously mounted DMG.
|
||||
//
|
||||
// Produces:
|
||||
// - application/json
|
||||
//
|
||||
// Parameters:
|
||||
// + name: mount_point
|
||||
// in: path
|
||||
// description: mount point of DMG
|
||||
// required: true
|
||||
// type: string
|
||||
// + name: dmg_path
|
||||
// in: query
|
||||
// description: path to DMG
|
||||
// type: string
|
||||
rg.POST("/unmount", func(c *gin.Context) {
|
||||
ctx := mount.Context{}
|
||||
if err := c.ShouldBindJSON(&ctx); err != nil {
|
||||
c.IndentedJSON(http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
if err := ctx.Unmount(); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"success": true})
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
package ota
|
||||
@@ -0,0 +1 @@
|
||||
package pongo
|
||||
@@ -0,0 +1,39 @@
|
||||
// Package routes contains all the routes for the API
|
||||
package routes
|
||||
|
||||
import (
|
||||
"github.com/blacktop/ipsw/api/server/routes/daemon"
|
||||
"github.com/blacktop/ipsw/api/server/routes/devicelist"
|
||||
"github.com/blacktop/ipsw/api/server/routes/download"
|
||||
"github.com/blacktop/ipsw/api/server/routes/dsc"
|
||||
"github.com/blacktop/ipsw/api/server/routes/extract"
|
||||
"github.com/blacktop/ipsw/api/server/routes/idev"
|
||||
"github.com/blacktop/ipsw/api/server/routes/info"
|
||||
"github.com/blacktop/ipsw/api/server/routes/ipsw"
|
||||
"github.com/blacktop/ipsw/api/server/routes/kernel"
|
||||
"github.com/blacktop/ipsw/api/server/routes/macho"
|
||||
"github.com/blacktop/ipsw/api/server/routes/mount"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// Add adds the command routes to the router
|
||||
func Add(rg *gin.RouterGroup) {
|
||||
daemon.AddRoutes(rg)
|
||||
devicelist.AddRoutes(rg)
|
||||
download.AddRoutes(rg)
|
||||
// dtree.AddRoutes(rg) // TODO: add dtree routes
|
||||
dsc.AddRoutes(rg)
|
||||
extract.AddRoutes(rg)
|
||||
idev.AddRoutes(rg)
|
||||
// img4.AddRoutes(rg) // TODO: add img4 routes
|
||||
info.AddRoutes(rg)
|
||||
ipsw.AddRoutes(rg)
|
||||
kernel.AddRoutes(rg)
|
||||
macho.AddRoutes(rg)
|
||||
// mdevs.AddRoutes(rg) // TODO: add mdevs routes
|
||||
mount.AddRoutes(rg)
|
||||
// ota.AddRoutes(rg) // TODO: add ota routes
|
||||
// pongo.AddRoutes(rg) // TODO: add pongo routes
|
||||
// sepfw.AddRoutes(rg) // TODO: add sepfw routes
|
||||
// symbolicate.AddRoutes(rg) // TODO: add symbolicate routes
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
package sepfw
|
||||
@@ -0,0 +1 @@
|
||||
package symbolicate
|
||||
@@ -0,0 +1,121 @@
|
||||
// Package server The ipsw API.
|
||||
//
|
||||
// Schemes: http
|
||||
// Host: localhost:8080
|
||||
// BasePath: /v1
|
||||
// Version: v1.0
|
||||
//
|
||||
// Consumes:
|
||||
// - application/json
|
||||
//
|
||||
// Produces:
|
||||
// - application/json
|
||||
//
|
||||
// swagger:meta
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/apex/log"
|
||||
"github.com/blacktop/ipsw/api"
|
||||
"github.com/blacktop/ipsw/api/server/routes"
|
||||
"github.com/blacktop/ipsw/api/types"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// Config is the server config
|
||||
type Config struct {
|
||||
Host string
|
||||
Port int
|
||||
Socket string
|
||||
Debug bool
|
||||
}
|
||||
|
||||
// Server is the main server struct
|
||||
type Server struct {
|
||||
router *gin.Engine
|
||||
server *http.Server
|
||||
conf *Config
|
||||
}
|
||||
|
||||
// NewServer creates a new server
|
||||
func NewServer(conf *Config) *Server {
|
||||
return &Server{
|
||||
router: gin.Default(),
|
||||
conf: conf,
|
||||
}
|
||||
}
|
||||
|
||||
// Start starts the server
|
||||
func (s *Server) Start() error {
|
||||
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
||||
defer stop()
|
||||
|
||||
s.router.GET("/version", func(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, types.Version{
|
||||
ApiVersion: api.DefaultVersion,
|
||||
OSType: runtime.GOOS,
|
||||
BuilderVersion: types.BuildVersion,
|
||||
})
|
||||
})
|
||||
|
||||
rg := s.router.Group("/v" + api.DefaultVersion)
|
||||
|
||||
routes.Add(rg)
|
||||
|
||||
s.server = &http.Server{
|
||||
Addr: fmt.Sprintf(":%d", s.conf.Port),
|
||||
Handler: s.router,
|
||||
}
|
||||
|
||||
go func() {
|
||||
if len(s.conf.Socket) > 0 {
|
||||
l, err := net.Listen("unix", filepath.Clean(s.conf.Socket))
|
||||
if err != nil {
|
||||
log.Fatalf("server: failed to listen: %v\n", err)
|
||||
}
|
||||
if err := s.server.Serve(l); err != nil && err != http.ErrServerClosed {
|
||||
log.Fatalf("server: failed to serve: %v\n", err)
|
||||
}
|
||||
} else {
|
||||
if err := s.server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
log.Fatalf("server: failed to listen and serve: %v\n", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Listen for the interrupt signal.
|
||||
<-ctx.Done()
|
||||
|
||||
// Restore default behavior on the interrupt signal and notify user of shutdown.
|
||||
stop()
|
||||
|
||||
log.Warn("Shutting down gracefully: Press Ctrl+C again to force")
|
||||
|
||||
return s.Stop()
|
||||
}
|
||||
|
||||
// Stop stops the server
|
||||
func (s *Server) Stop() error {
|
||||
// The context is used to inform the server it has 5 seconds to finish
|
||||
// the request it is currently handling
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if err := s.server.Shutdown(ctx); err != nil {
|
||||
return fmt.Errorf("server forced to shutdown: %v", err)
|
||||
}
|
||||
|
||||
log.Info("Server Exiting")
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,582 @@
|
||||
{
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"schemes": [
|
||||
"http"
|
||||
],
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"title": "The ipsw API.",
|
||||
"version": "v1.0"
|
||||
},
|
||||
"host": "localhost:8080",
|
||||
"basePath": "/v1",
|
||||
"paths": {
|
||||
"/_ping": {
|
||||
"get": {
|
||||
"description": "This will return \"OK\" if the daemon is running.",
|
||||
"tags": [
|
||||
"Daemon"
|
||||
],
|
||||
"summary": "Ping",
|
||||
"operationId": "getDaemonPing"
|
||||
},
|
||||
"head": {
|
||||
"description": "This will return if 200 the daemon is running.",
|
||||
"tags": [
|
||||
"Daemon"
|
||||
],
|
||||
"summary": "Ping",
|
||||
"operationId": "headDaemonPing"
|
||||
}
|
||||
},
|
||||
"/device_list": {
|
||||
"get": {
|
||||
"description": "This will return JSON of all XCode devices.",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"DeviceList"
|
||||
],
|
||||
"summary": "List XCode Devices.",
|
||||
"operationId": "getDeviceList"
|
||||
}
|
||||
},
|
||||
"/download/ipsw/ios/latest/build": {
|
||||
"get": {
|
||||
"description": "Get latest iOS build.",
|
||||
"tags": [
|
||||
"Download"
|
||||
],
|
||||
"summary": "Latest iOS Build",
|
||||
"operationId": "getDownloadLatestIPSWsBuild"
|
||||
}
|
||||
},
|
||||
"/download/ipsw/ios/latest/version": {
|
||||
"get": {
|
||||
"description": "Get latest iOS version.",
|
||||
"tags": [
|
||||
"Download"
|
||||
],
|
||||
"summary": "Latest iOS Version",
|
||||
"operationId": "getDownloadLatestIPSWsVersion"
|
||||
}
|
||||
},
|
||||
"/dsc/imports": {
|
||||
"get": {
|
||||
"description": "Get list of dylibs that import a given dylib.",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"DSC"
|
||||
],
|
||||
"summary": "Imports",
|
||||
"operationId": "getDscImports",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "path to IPSW",
|
||||
"name": "path",
|
||||
"in": "query",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "dylib to search for",
|
||||
"name": "dylib",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/dsc/info": {
|
||||
"get": {
|
||||
"description": "Get info about a given DSC",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"DSC"
|
||||
],
|
||||
"summary": "Info",
|
||||
"operationId": "getDscInfo",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "path to IPSW",
|
||||
"name": "path",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/dsc/macho": {
|
||||
"get": {
|
||||
"description": "Get MachO info for a given dylib in the DSC.",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"DSC"
|
||||
],
|
||||
"summary": "MachO",
|
||||
"operationId": "getDscMacho",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "path to IPSW",
|
||||
"name": "path",
|
||||
"in": "query",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "dylib to search for",
|
||||
"name": "dylib",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/dsc/str": {
|
||||
"get": {
|
||||
"description": "Get strings in the DSC that match a given pattern.",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"DSC"
|
||||
],
|
||||
"summary": "Strings",
|
||||
"operationId": "getDscStrings",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "path to IPSW",
|
||||
"name": "path",
|
||||
"in": "query",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "regex to search for",
|
||||
"name": "pattern",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/dsc/symaddr": {
|
||||
"get": {
|
||||
"description": "Get symbols addresses in the DSC that match a given lookup JSON payload.",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"DSC"
|
||||
],
|
||||
"summary": "Symbols",
|
||||
"operationId": "getDscSymbols",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "path to IPSW",
|
||||
"name": "path",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/extract/dmg": {
|
||||
"get": {
|
||||
"description": "Extract DMGs from an IPSW.",
|
||||
"tags": [
|
||||
"Extract"
|
||||
],
|
||||
"summary": "DMG",
|
||||
"operationId": "getExtractDmg"
|
||||
}
|
||||
},
|
||||
"/extract/dsc": {
|
||||
"get": {
|
||||
"description": "Extract dyld_shared_caches from an IPSW.",
|
||||
"tags": [
|
||||
"Extract"
|
||||
],
|
||||
"summary": "DSC",
|
||||
"operationId": "getExtractDsc"
|
||||
}
|
||||
},
|
||||
"/extract/kbag": {
|
||||
"get": {
|
||||
"description": "Extract KBAGs from an IPSW.",
|
||||
"tags": [
|
||||
"Extract"
|
||||
],
|
||||
"summary": "KBAG",
|
||||
"operationId": "getExtractKbags"
|
||||
}
|
||||
},
|
||||
"/extract/kernel": {
|
||||
"get": {
|
||||
"description": "Extract kernelcaches from an IPSW.",
|
||||
"tags": [
|
||||
"Extract"
|
||||
],
|
||||
"summary": "Kernel",
|
||||
"operationId": "getExtractKernel"
|
||||
}
|
||||
},
|
||||
"/extract/pattern": {
|
||||
"get": {
|
||||
"description": "Extract files from an IPSW that match a given pattern.",
|
||||
"tags": [
|
||||
"Extract"
|
||||
],
|
||||
"summary": "Pattern",
|
||||
"operationId": "getExtractPattern"
|
||||
}
|
||||
},
|
||||
"/idev/info": {
|
||||
"get": {
|
||||
"description": "Get info about USB connected devices.",
|
||||
"tags": [
|
||||
"USB"
|
||||
],
|
||||
"summary": "Info",
|
||||
"operationId": "getIdevInfo"
|
||||
}
|
||||
},
|
||||
"/info/ipsw": {
|
||||
"get": {
|
||||
"description": "Get IPSW info.",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Info"
|
||||
],
|
||||
"summary": "IPSW",
|
||||
"operationId": "getIpswInfo",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "path to IPSW",
|
||||
"name": "path",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/info/ipsw/remote": {
|
||||
"get": {
|
||||
"description": "Get remote IPSW info.",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Info"
|
||||
],
|
||||
"summary": "Remote IPSW",
|
||||
"operationId": "getRemoteIpswInfo",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "url to IPSW",
|
||||
"name": "url",
|
||||
"in": "query",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "http proxy to use",
|
||||
"name": "proxy",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"description": "ignore TLS errors",
|
||||
"name": "insecure",
|
||||
"in": "query"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/info/ota": {
|
||||
"get": {
|
||||
"description": "Get OTA info.",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Info"
|
||||
],
|
||||
"summary": "OTA",
|
||||
"operationId": "getOtaInfo",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "path to OTA",
|
||||
"name": "path",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/info/ota/remote": {
|
||||
"get": {
|
||||
"description": "Get remote OTA info.",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Info"
|
||||
],
|
||||
"summary": "Remote OTA",
|
||||
"operationId": "getRemoteOtaInfo",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "url to OTA",
|
||||
"name": "url",
|
||||
"in": "query",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "http proxy to use",
|
||||
"name": "proxy",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"description": "ignore TLS errors",
|
||||
"name": "insecure",
|
||||
"in": "query"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/ipsw/fs/ents": {
|
||||
"get": {
|
||||
"description": "Get IPSW Filesystem DMG MachO entitlements.",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"IPSW"
|
||||
],
|
||||
"summary": "Entitlements",
|
||||
"operationId": "getIpswFsEntitlements",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "path to IPSW",
|
||||
"name": "path",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/ipsw/fs/files": {
|
||||
"get": {
|
||||
"description": "Get IPSW Filesystem DMG file listing.",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"IPSW"
|
||||
],
|
||||
"summary": "Files",
|
||||
"operationId": "getIpswFsFiles",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "path to IPSW",
|
||||
"name": "path",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/kernel/kexts": {
|
||||
"get": {
|
||||
"description": "Get kernelcache KEXTs info.",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Kernel"
|
||||
],
|
||||
"summary": "Kexts",
|
||||
"operationId": "getKernelKexts",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "path to kernelcache",
|
||||
"name": "path",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/kernel/syscall": {
|
||||
"get": {
|
||||
"description": "Get kernelcache syscalls info.",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Kernel"
|
||||
],
|
||||
"summary": "Syscalls",
|
||||
"operationId": "getKernelSyscalls",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "path to kernelcache",
|
||||
"name": "path",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/kernel/version": {
|
||||
"get": {
|
||||
"description": "Get kernelcache version.",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Kernel"
|
||||
],
|
||||
"summary": "Version",
|
||||
"operationId": "getKernelVersion",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "path to kernelcache",
|
||||
"name": "path",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/macho/info": {
|
||||
"get": {
|
||||
"description": "Get MachO info.",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"MachO"
|
||||
],
|
||||
"summary": "Info",
|
||||
"operationId": "getMachoInfo",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "path to MachO",
|
||||
"name": "path",
|
||||
"in": "query",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "architecture to get info for in universal MachO",
|
||||
"name": "arch",
|
||||
"in": "query"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/mount/{type}": {
|
||||
"post": {
|
||||
"description": "Mount a DMG inside a given IPSW.",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Mount"
|
||||
],
|
||||
"summary": "Mount",
|
||||
"operationId": "postMount",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "type of DMG to mount",
|
||||
"name": "type",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "path to IPSW",
|
||||
"name": "path",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/unmount": {
|
||||
"post": {
|
||||
"description": "Unmount a previously mounted DMG.",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Mount"
|
||||
],
|
||||
"summary": "Unmount",
|
||||
"operationId": "postUnmount",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "mount point of DMG",
|
||||
"name": "mount_point",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "path to DMG",
|
||||
"name": "dmg_path",
|
||||
"in": "query"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/version": {
|
||||
"get": {
|
||||
"description": "This will return the daemon version info.",
|
||||
"tags": [
|
||||
"Daemon"
|
||||
],
|
||||
"summary": "Version",
|
||||
"operationId": "getDaemonVersion"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package types
|
||||
|
||||
var (
|
||||
BuildVersion string
|
||||
BuildTime string
|
||||
)
|
||||
|
||||
// Version is the version struct
|
||||
type Version struct {
|
||||
ApiVersion string
|
||||
OSType string
|
||||
BuilderVersion string
|
||||
}
|
||||
@@ -25,16 +25,14 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/apex/log"
|
||||
"github.com/blacktop/ipsw/internal/commands/extract"
|
||||
"github.com/blacktop/ipsw/internal/download"
|
||||
"github.com/blacktop/ipsw/internal/utils"
|
||||
"github.com/blacktop/ipsw/pkg/dyld"
|
||||
"github.com/blacktop/ipsw/pkg/info"
|
||||
"github.com/blacktop/ipsw/pkg/kernelcache"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
@@ -161,11 +159,6 @@ var ipswCmd = &cobra.Command{
|
||||
dFlg.Build = dev.BuildVersion
|
||||
}
|
||||
|
||||
var destPath string
|
||||
if len(output) > 0 {
|
||||
destPath = filepath.Clean(output)
|
||||
}
|
||||
|
||||
if len(device) > 0 {
|
||||
db, err := info.GetIpswDB()
|
||||
if err != nil {
|
||||
@@ -332,70 +325,68 @@ var ipswCmd = &cobra.Command{
|
||||
"signed": ipsw.Signed,
|
||||
}).Info("Parsing remote IPSW")
|
||||
|
||||
zr, err := download.NewRemoteZipReader(ipsw.URL, &download.RemoteConfig{
|
||||
config := &extract.Config{
|
||||
IPSW: "",
|
||||
URL: ipsw.URL,
|
||||
Pattern: remotePattern,
|
||||
Arches: dyldArches,
|
||||
Proxy: proxy,
|
||||
Insecure: insecure,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create remote zip reader of IPSW: %v", err)
|
||||
DMGs: false,
|
||||
DmgType: "",
|
||||
Flatten: flat,
|
||||
Progress: true,
|
||||
Output: output,
|
||||
}
|
||||
|
||||
inf, err := info.ParseZipFiles(zr.File)
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse remote IPSW metadata: %v", err)
|
||||
return fmt.Errorf("failed to get current working directory: %v", err)
|
||||
}
|
||||
|
||||
folder, err := inf.GetFolder()
|
||||
if err != nil {
|
||||
log.Errorf("failed to get folder from remote zip metadata: %v", err)
|
||||
}
|
||||
destPath = filepath.Join(destPath, folder)
|
||||
|
||||
// REMOTE KERNEL MODE
|
||||
if remoteKernel {
|
||||
log.Info("Extracting remote kernelcache")
|
||||
if err := kernelcache.RemoteParse(zr, destPath); err != nil {
|
||||
return fmt.Errorf("failed to download kernelcache from remote IPSW: %v", err)
|
||||
artifacts, err := extract.Kernelcache(config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to extract kernelcache from remote IPSW: %v", err)
|
||||
}
|
||||
for _, artifact := range artifacts {
|
||||
utils.Indent(log.Info, 2)("Extracted " + strings.TrimPrefix(cwd, artifact))
|
||||
}
|
||||
}
|
||||
// REMOTE DSC MODE
|
||||
if remoteDSC {
|
||||
log.Info("Extracting remote dyld_shared_cache(s)")
|
||||
sysDMG, err := inf.GetSystemOsDmg()
|
||||
artifacts, err := extract.DSC(config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("only iOS16.x/macOS13.x supported: failed to get SystemOS DMG from remote zip metadata: %v", err)
|
||||
return err
|
||||
}
|
||||
if len(sysDMG) == 0 {
|
||||
return fmt.Errorf("only iOS16.x/macOS13.x supported: no SystemOS DMG found in remote zip metadata")
|
||||
}
|
||||
tmpDIR, err := os.MkdirTemp("", "ipsw_extract_remote_dyld")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create temporary directory to store SystemOS DMG: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDIR)
|
||||
if err := utils.RemoteUnzip(zr.File, regexp.MustCompile(fmt.Sprintf("^%s$", sysDMG)), tmpDIR, true, true); err != nil {
|
||||
return fmt.Errorf("failed to extract SystemOS DMG from remote IPSW: %v", err)
|
||||
}
|
||||
|
||||
if err := dyld.ExtractFromDMG(inf, filepath.Join(tmpDIR, sysDMG), destPath, viper.GetStringSlice("extract.dyld-arch")); err != nil {
|
||||
return fmt.Errorf("failed to extract dyld_shared_cache(s) from remote IPSW: %v", err)
|
||||
for _, artifact := range artifacts {
|
||||
utils.Indent(log.Info, 2)("Extracted " + strings.TrimPrefix(cwd, artifact))
|
||||
}
|
||||
}
|
||||
// PATTERN MATCHING MODE
|
||||
if len(remotePattern) > 0 {
|
||||
dlRE, err := regexp.Compile(remotePattern)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to compile regex for pattern '%s': %v", remotePattern, err)
|
||||
}
|
||||
log.Infof("Downloading files matching pattern %#v", remotePattern)
|
||||
if err := utils.RemoteUnzip(zr.File, dlRE, destPath, flat, true); err != nil {
|
||||
return fmt.Errorf("failed to download pattern matching files from remote IPSW: %v", err)
|
||||
artifacts, err := extract.Search(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, artifact := range artifacts {
|
||||
utils.Indent(log.Info, 2)("Extracted " + strings.TrimPrefix(cwd, artifact))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else { // NORMAL MODE
|
||||
for _, i := range ipsws {
|
||||
destName := getDestName(i.URL, removeCommas)
|
||||
if len(output) > 0 {
|
||||
destName = filepath.Join(filepath.Clean(output), destName)
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Dir(destName), 0755); err != nil {
|
||||
return fmt.Errorf("failed to create directory: %v", err)
|
||||
}
|
||||
if _, err := os.Stat(destName); os.IsNotExist(err) {
|
||||
log.WithFields(log.Fields{
|
||||
"device": i.Identifier,
|
||||
|
||||
@@ -359,7 +359,7 @@ var otaDLCmd = &cobra.Command{
|
||||
|
||||
if remoteKernel { // REMOTE KERNEL MODE
|
||||
log.Info("Extracting remote kernelcache")
|
||||
err = kernelcache.RemoteParse(zr, destPath)
|
||||
_, err = kernelcache.RemoteParse(zr, destPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to download kernelcache from remote ota: %v", err)
|
||||
}
|
||||
@@ -413,7 +413,7 @@ var otaDLCmd = &cobra.Command{
|
||||
return fmt.Errorf("failed to compile regex for pattern '%s': %v", remotePattern, err)
|
||||
}
|
||||
log.Infof("Downloading files matching pattern %#v", remotePattern)
|
||||
if err := utils.RemoteUnzip(zr.File, re, destPath, flat, true); err != nil {
|
||||
if _, err := utils.SearchZip(zr.File, re, destPath, flat, true); err != nil {
|
||||
utils.Indent(log.Warn, 2)("0 files matched pattern in remote OTA zip. Now checking payloadv2 payloads...")
|
||||
rfiles, err := ota.RemoteList(zr)
|
||||
if err != nil {
|
||||
|
||||
@@ -26,15 +26,14 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/apex/log"
|
||||
"github.com/blacktop/ipsw/internal/commands/extract"
|
||||
"github.com/blacktop/ipsw/internal/download"
|
||||
"github.com/blacktop/ipsw/internal/utils"
|
||||
"github.com/blacktop/ipsw/pkg/info"
|
||||
"github.com/blacktop/ipsw/pkg/kernelcache"
|
||||
"github.com/blacktop/ipsw/pkg/plist"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
@@ -307,41 +306,41 @@ var wikiCmd = &cobra.Command{
|
||||
d, v, b := download.ParseIpswURLString(url)
|
||||
log.WithFields(log.Fields{"devices": d, "build": b, "version": v}).Info("Parsing remote IPSW")
|
||||
|
||||
zr, err := download.NewRemoteZipReader(url, &download.RemoteConfig{
|
||||
config := &extract.Config{
|
||||
URL: url,
|
||||
Pattern: pattern,
|
||||
Proxy: proxy,
|
||||
Insecure: insecure,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create remote zip reader of IPSW: %v", err)
|
||||
Flatten: flat,
|
||||
Progress: true,
|
||||
Output: output,
|
||||
}
|
||||
|
||||
inf, err := info.ParseZipFiles(zr.File)
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse remote IPSW metadata: %v", err)
|
||||
return fmt.Errorf("failed to get current working directory: %v", err)
|
||||
}
|
||||
|
||||
folder, err := inf.GetFolder()
|
||||
if err != nil {
|
||||
log.Errorf("failed to get folder from remote zip metadata: %v", err)
|
||||
}
|
||||
destPath = filepath.Join(destPath, folder)
|
||||
|
||||
// REMOTE KERNEL MODE
|
||||
if kernel {
|
||||
log.Info("Extracting remote kernelcache")
|
||||
if err := kernelcache.RemoteParse(zr, destPath); err != nil {
|
||||
return fmt.Errorf("failed to download kernelcache from remote IPSW: %v", err)
|
||||
artifacts, err := extract.Kernelcache(config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to extract kernelcache from remote IPSW: %v", err)
|
||||
}
|
||||
for _, artifact := range artifacts {
|
||||
utils.Indent(log.Info, 2)("Extracted " + strings.TrimPrefix(cwd, artifact))
|
||||
}
|
||||
}
|
||||
// PATTERN MATCHING MODE
|
||||
if len(pattern) > 0 {
|
||||
dlRE, err := regexp.Compile(pattern)
|
||||
log.Infof("Downloading files matching pattern %#v", pattern)
|
||||
artifacts, err := extract.Search(config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to compile regex for pattern '%s': %v", pattern, err)
|
||||
return err
|
||||
}
|
||||
log.Infof("Downloading files that contain: %s", pattern)
|
||||
if err := utils.RemoteUnzip(zr.File, dlRE, destPath, flat, true); err != nil {
|
||||
return fmt.Errorf("failed to download pattern matching files from remote IPSW: %v", err)
|
||||
for _, artifact := range artifacts {
|
||||
utils.Indent(log.Info, 2)("Extracted " + strings.TrimPrefix(cwd, artifact))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import (
|
||||
|
||||
"github.com/apex/log"
|
||||
"github.com/blacktop/go-macho"
|
||||
dscCmd "github.com/blacktop/ipsw/internal/commands/dsc"
|
||||
"github.com/blacktop/ipsw/internal/search"
|
||||
"github.com/blacktop/ipsw/pkg/dyld"
|
||||
"github.com/pkg/errors"
|
||||
@@ -127,52 +128,24 @@ var dyldImportsCmd = &cobra.Command{
|
||||
fmt.Print(title)
|
||||
fmt.Println(strings.Repeat("=", len(title)-2))
|
||||
|
||||
if f.SupportsDylibPrebuiltLoader() {
|
||||
importedBy, err := dscCmd.GetDylibsThatImport(f, image.Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get dylibs that import %s: %v", image.Name, err)
|
||||
}
|
||||
|
||||
if len(importedBy.DSC) > 0 {
|
||||
fmt.Println("\nIn DSC (Dylibs)")
|
||||
fmt.Println("---------------")
|
||||
for _, img := range f.Images {
|
||||
pbl, err := f.GetDylibPrebuiltLoader(img.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, dep := range pbl.Dependents {
|
||||
if strings.EqualFold(dep.Name, image.Name) {
|
||||
fmt.Println(img.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, img := range f.Images {
|
||||
m, err := img.GetPartialMacho()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, imp := range m.ImportedLibraries() {
|
||||
if strings.EqualFold(imp, image.Name) {
|
||||
fmt.Println(img.Name)
|
||||
}
|
||||
}
|
||||
m.Close()
|
||||
for _, img := range importedBy.DSC {
|
||||
fmt.Println(img)
|
||||
}
|
||||
}
|
||||
|
||||
if f.SupportsPrebuiltLoaderSet() {
|
||||
if len(importedBy.Apps) > 0 {
|
||||
fmt.Println("\nIn FileSystem DMG (Apps)")
|
||||
fmt.Println("------------------------")
|
||||
if err := f.ForEachLaunchLoaderSet(func(execPath string, pset *dyld.PrebuiltLoaderSet) {
|
||||
for _, loader := range pset.Loaders {
|
||||
for _, dep := range loader.Dependents {
|
||||
if strings.EqualFold(dep.Name, image.Name) {
|
||||
if execPath != loader.Path {
|
||||
fmt.Printf("%s (%s)\n", execPath, loader.Path)
|
||||
} else {
|
||||
fmt.Println(execPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}); err != nil {
|
||||
return err
|
||||
for _, img := range importedBy.Apps {
|
||||
fmt.Println(img)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ import (
|
||||
"github.com/alecthomas/chroma/quick"
|
||||
"github.com/apex/log"
|
||||
"github.com/blacktop/go-macho"
|
||||
"github.com/blacktop/go-macho/pkg/codesign"
|
||||
dscCmd "github.com/blacktop/ipsw/internal/commands/dsc"
|
||||
"github.com/blacktop/ipsw/internal/utils"
|
||||
"github.com/blacktop/ipsw/pkg/dyld"
|
||||
"github.com/fullsailor/pkcs7"
|
||||
@@ -64,27 +64,6 @@ func init() {
|
||||
dyldInfoCmd.MarkZshCompPositionalArgumentFile(1, "dyld_shared_cache*")
|
||||
}
|
||||
|
||||
type dylib struct {
|
||||
Index int `json:"index,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
UUID string `json:"uuid,omitempty"`
|
||||
LoadAddress uint64 `json:"load_address,omitempty"`
|
||||
}
|
||||
|
||||
type dyldInfo struct {
|
||||
Magic string `json:"magic,omitempty"`
|
||||
UUID string `json:"uuid,omitempty"`
|
||||
Platform string `json:"platform,omitempty"`
|
||||
MaxSlide int `json:"max_slide,omitempty"`
|
||||
SubCacheArrayCount int `json:"num_sub_caches,omitempty"`
|
||||
SubCacheGroupID int `json:"sub_cache_group_id,omitempty"`
|
||||
SymSubCacheUUID string `json:"sym_sub_cache_uuid,omitempty"`
|
||||
Mappings map[string][]dyld.CacheMappingWithSlideInfo `json:"mappings,omitempty"`
|
||||
CodeSignature map[string]codesign.CodeSignature `json:"code_signature,omitempty"`
|
||||
Dylibs []dylib `json:"dylibs,omitempty"`
|
||||
}
|
||||
|
||||
// dyldInfoCmd represents the info command
|
||||
var dyldInfoCmd = &cobra.Command{
|
||||
Use: "info <dyld_shared_cache>",
|
||||
@@ -145,54 +124,15 @@ var dyldInfoCmd = &cobra.Command{
|
||||
defer f.Close()
|
||||
|
||||
if outAsJSON {
|
||||
dinfo := dyldInfo{
|
||||
Magic: f.Headers[f.UUID].Magic.String(),
|
||||
UUID: f.UUID.String(),
|
||||
Platform: f.Headers[f.UUID].Platform.String(),
|
||||
MaxSlide: int(f.Headers[f.UUID].MaxSlide),
|
||||
dinfo, err := dscCmd.GetInfo(f)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get DSC info: %s", err)
|
||||
}
|
||||
|
||||
dinfo.Mappings = make(map[string][]dyld.CacheMappingWithSlideInfo)
|
||||
|
||||
for u, mp := range f.MappingsWithSlideInfo {
|
||||
for _, m := range mp {
|
||||
dinfo.Mappings[u.String()] = append(dinfo.Mappings[u.String()], *m)
|
||||
}
|
||||
}
|
||||
|
||||
dinfo.CodeSignature = make(map[string]codesign.CodeSignature)
|
||||
|
||||
if showSignature {
|
||||
for u, cs := range f.CodeSignatures {
|
||||
dinfo.CodeSignature[u.String()] = *cs
|
||||
}
|
||||
}
|
||||
|
||||
if showDylibs {
|
||||
for idx, img := range f.Images {
|
||||
m, err := img.GetPartialMacho()
|
||||
if err != nil {
|
||||
continue
|
||||
// return fmt.Errorf("failed to create partial MachO for image %s: %v", img.Name, err)
|
||||
}
|
||||
dinfo.Dylibs = append(dinfo.Dylibs, dylib{
|
||||
Index: idx + 1,
|
||||
Name: img.Name,
|
||||
Version: m.SourceVersion().Version.String(),
|
||||
UUID: m.UUID().String(),
|
||||
LoadAddress: img.Info.Address,
|
||||
})
|
||||
m.Close()
|
||||
}
|
||||
}
|
||||
|
||||
j, err := json.Marshal(dinfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println(string(j))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
+13
-120
@@ -22,16 +22,13 @@ THE SOFTWARE.
|
||||
package dyld
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/apex/log"
|
||||
"github.com/blacktop/ipsw/internal/utils"
|
||||
dscCmd "github.com/blacktop/ipsw/internal/commands/dsc"
|
||||
"github.com/blacktop/ipsw/pkg/dyld"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -40,11 +37,7 @@ import (
|
||||
|
||||
func init() {
|
||||
DyldCmd.AddCommand(StrSearchCmd)
|
||||
StrSearchCmd.Flags().BoolP("insensitive", "i", false, "Case-insensitive search")
|
||||
StrSearchCmd.Flags().BoolP("contains", "c", false, "Match strings that contain the search substring")
|
||||
StrSearchCmd.Flags().StringP("pattern", "p", "", "Regex match strings (FAST)")
|
||||
viper.BindPFlag("dyld.str.insensitive", StrSearchCmd.Flags().Lookup("insensitive"))
|
||||
viper.BindPFlag("dyld.str.contains", StrSearchCmd.Flags().Lookup("contains"))
|
||||
StrSearchCmd.Flags().StringP("pattern", "p", "", "Regex match strings")
|
||||
viper.BindPFlag("dyld.str.pattern", StrSearchCmd.Flags().Lookup("pattern"))
|
||||
}
|
||||
|
||||
@@ -59,20 +52,8 @@ var StrSearchCmd = &cobra.Command{
|
||||
log.SetLevel(log.DebugLevel)
|
||||
}
|
||||
|
||||
var err error
|
||||
var strRE *regexp.Regexp
|
||||
|
||||
insensitive := viper.GetBool("dyld.str.insensitive")
|
||||
contains := viper.GetBool("dyld.str.contains")
|
||||
pattern := viper.GetString("dyld.str.pattern")
|
||||
|
||||
if len(pattern) > 0 {
|
||||
strRE, err = regexp.Compile(pattern)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid regex: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
dscPath := filepath.Clean(args[0])
|
||||
|
||||
fileInfo, err := os.Lstat(dscPath)
|
||||
@@ -99,106 +80,18 @@ var StrSearchCmd = &cobra.Command{
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
for _, i := range f.Images {
|
||||
log.Debugf("Searching image %s", i.Name)
|
||||
m, err := i.GetMacho()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// cstrings
|
||||
for _, sec := range m.Sections {
|
||||
if sec.Flags.IsCstringLiterals() || sec.Seg == "__TEXT" && sec.Name == "__const" {
|
||||
uuid, off, err := f.GetOffset(sec.Addr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get offset for %s.%s: %v", sec.Seg, sec.Name, err)
|
||||
}
|
||||
dat, err := f.ReadBytesForUUID(uuid, int64(off), sec.Size)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read cstrings in %s.%s: %v", sec.Seg, sec.Name, err)
|
||||
}
|
||||
|
||||
csr := bytes.NewBuffer(dat)
|
||||
|
||||
for {
|
||||
pos := sec.Addr + uint64(csr.Cap()-csr.Len())
|
||||
|
||||
s, err := csr.ReadString('\x00')
|
||||
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read string: %v", err)
|
||||
}
|
||||
|
||||
s = strings.Trim(s, "\x00")
|
||||
|
||||
if len(s) > 0 {
|
||||
if (sec.Seg == "__TEXT" && sec.Name == "__const") && !utils.IsASCII(s) {
|
||||
continue // skip non-ascii strings when dumping __TEXT.__const
|
||||
}
|
||||
if len(pattern) > 0 {
|
||||
if strRE.MatchString(s) {
|
||||
fmt.Printf("%#x: (%s)\t%#v\n", pos, filepath.Base(i.Name), s)
|
||||
}
|
||||
} else {
|
||||
if contains && insensitive {
|
||||
if strings.Contains(strings.ToLower(s), strings.ToLower(args[1])) {
|
||||
fmt.Printf("%#x: (%s)\t%#v\n", pos, filepath.Base(i.Name), s)
|
||||
}
|
||||
} else if contains {
|
||||
if strings.Contains(s, args[1]) {
|
||||
fmt.Printf("%#x: (%s)\t%#v\n", pos, filepath.Base(i.Name), s)
|
||||
}
|
||||
} else if insensitive {
|
||||
if len(s) == len(args[1]) && strings.EqualFold(s, args[1]) {
|
||||
fmt.Printf("%#x: (%s)\t%#v\n", pos, filepath.Base(i.Name), s)
|
||||
}
|
||||
} else {
|
||||
if len(s) == len(args[1]) && s == args[1] {
|
||||
fmt.Printf("%#x: (%s)\t%#v\n", pos, filepath.Base(i.Name), s)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// objc cfstrings
|
||||
if cfstrs, err := m.GetCFStrings(); err == nil {
|
||||
if len(cfstrs) > 0 {
|
||||
for _, cfstr := range cfstrs {
|
||||
if len(pattern) > 0 {
|
||||
if strRE.MatchString(cfstr.Name) {
|
||||
fmt.Printf("%#09x: (%s)\t%#v\n", cfstr.Address, filepath.Base(i.Name), cfstr.Name)
|
||||
}
|
||||
} else {
|
||||
if contains && insensitive {
|
||||
if strings.Contains(strings.ToLower(cfstr.Name), strings.ToLower(args[1])) {
|
||||
fmt.Printf("%#09x: (%s)\t%#v\n", cfstr.Address, filepath.Base(i.Name), cfstr.Name)
|
||||
}
|
||||
} else if contains {
|
||||
if strings.Contains(cfstr.Name, args[1]) {
|
||||
fmt.Printf("%#09x: (%s)\t%#v\n", cfstr.Address, filepath.Base(i.Name), cfstr.Name)
|
||||
}
|
||||
} else if insensitive {
|
||||
if len(cfstr.Name) == len(args[1]) && strings.EqualFold(cfstr.Name, args[1]) {
|
||||
fmt.Printf("%#09x: (%s)\t%#v\n", cfstr.Address, filepath.Base(i.Name), cfstr.Name)
|
||||
}
|
||||
} else {
|
||||
if len(cfstr.Name) == len(args[1]) && cfstr.Name == args[1] {
|
||||
fmt.Printf("%#09x: (%s)\t%#v\n", cfstr.Address, filepath.Base(i.Name), cfstr.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
log.Info("Searching for strings...")
|
||||
strs, err := dscCmd.GetStrings(f, pattern)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
|
||||
for _, str := range strs {
|
||||
fmt.Fprintf(w, "%s: %s\t%s\n", colorAddr("%#x", str.Address), str.String, symImageColor(str.Image))
|
||||
}
|
||||
w.Flush()
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
@@ -27,9 +27,9 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
|
||||
"github.com/apex/log"
|
||||
dscCmd "github.com/blacktop/ipsw/internal/commands/dsc"
|
||||
"github.com/blacktop/ipsw/internal/utils"
|
||||
"github.com/blacktop/ipsw/pkg/dyld"
|
||||
"github.com/fatih/color"
|
||||
@@ -100,147 +100,34 @@ var SymAddrCmd = &cobra.Command{
|
||||
/******************************************
|
||||
* Search for symbols in JSON lookup file *
|
||||
******************************************/
|
||||
var enc *json.Encoder
|
||||
var slin []dyld.Symbol
|
||||
var slout []dyld.Symbol
|
||||
var lookups []dscCmd.Symbol
|
||||
|
||||
symbolFile = filepath.Clean(symbolFile)
|
||||
sdata, _ := os.ReadFile(symbolFile)
|
||||
|
||||
if err := json.Unmarshal(sdata, &slin); err != nil {
|
||||
lookupData, err := os.ReadFile(filepath.Clean(symbolFile))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read symbol lookup JSON file %s: %v", symbolFile, err)
|
||||
}
|
||||
if err := json.Unmarshal(lookupData, &lookups); err != nil {
|
||||
return fmt.Errorf("failed to parse symbol lookup JSON file %s: %v", symbolFile, err)
|
||||
}
|
||||
|
||||
// group syms by image
|
||||
symages := make(map[string][]dyld.Symbol)
|
||||
for _, s := range slin {
|
||||
if len(s.Image) > 0 {
|
||||
image, err := f.Image(s.Image)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
symages[image.Name] = append(symages[image.Name], s)
|
||||
} else {
|
||||
symages["unknown"] = append(symages["unknown"], s)
|
||||
}
|
||||
}
|
||||
|
||||
if _, uhoh := symages["unknown"]; uhoh {
|
||||
log.Warn("you should supply 'image' fields for each symbol to GREATLY increase speed")
|
||||
}
|
||||
|
||||
for imageName, syms := range symages {
|
||||
if imageName == "unknown" {
|
||||
for _, s := range syms {
|
||||
found := false
|
||||
for _, image := range f.Images {
|
||||
if len(s.Regex) > 0 {
|
||||
re, err := regexp.Compile(s.Regex)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m, err := image.GetPartialMacho()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
image.ParseLocalSymbols(false)
|
||||
for _, lsym := range image.LocalSymbols {
|
||||
if re.MatchString(lsym.Name) {
|
||||
var sec string
|
||||
if lsym.Sect > 0 && int(lsym.Sect) <= len(m.Sections) {
|
||||
sec = fmt.Sprintf("%s.%s", m.Sections[lsym.Sect-1].Seg, m.Sections[lsym.Sect-1].Name)
|
||||
}
|
||||
slout = append(slout, dyld.Symbol{
|
||||
Name: lsym.Name,
|
||||
Address: lsym.Value,
|
||||
Type: lsym.Type.String(sec),
|
||||
Image: image.Name,
|
||||
Kind: dyld.LOCAL,
|
||||
})
|
||||
}
|
||||
}
|
||||
image.ParsePublicSymbols(false)
|
||||
for _, sym := range image.PublicSymbols {
|
||||
if re.MatchString(sym.Name) {
|
||||
sym.Image = filepath.Base(image.Name)
|
||||
slout = append(slout, *sym)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if sym, err := image.GetSymbol(s.Name); err == nil {
|
||||
if sym.Address > 0 {
|
||||
slout = append(slout, *sym)
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
log.Errorf("failed to find address for symbol %s", s.Name)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
image, err := f.Image(imageName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, s := range syms {
|
||||
if len(s.Regex) > 0 {
|
||||
re, err := regexp.Compile(s.Regex)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m, err := image.GetPartialMacho()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
image.ParseLocalSymbols(false)
|
||||
for _, lsym := range image.LocalSymbols {
|
||||
if re.MatchString(lsym.Name) {
|
||||
var sec string
|
||||
if lsym.Sect > 0 && int(lsym.Sect) <= len(m.Sections) {
|
||||
sec = fmt.Sprintf("%s.%s", m.Sections[lsym.Sect-1].Seg, m.Sections[lsym.Sect-1].Name)
|
||||
}
|
||||
slout = append(slout, dyld.Symbol{
|
||||
Name: lsym.Name,
|
||||
Address: lsym.Value,
|
||||
Type: lsym.Type.String(sec),
|
||||
Image: image.Name,
|
||||
Kind: dyld.LOCAL,
|
||||
})
|
||||
}
|
||||
}
|
||||
image.ParsePublicSymbols(false)
|
||||
for _, sym := range image.PublicSymbols {
|
||||
if re.MatchString(sym.Name) {
|
||||
sym.Image = filepath.Base(image.Name)
|
||||
slout = append(slout, *sym)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if sym, err := image.GetSymbol(s.Name); err == nil {
|
||||
slout = append(slout, *sym)
|
||||
} else {
|
||||
log.Errorf("failed to find address for symbol %s in image %s", s.Name, filepath.Base(image.Name))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
syms, err := dscCmd.GetSymbols(f, lookups)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to lookup symbols from lookup JSON file: %v", err)
|
||||
}
|
||||
|
||||
var enc *json.Encoder
|
||||
if len(jsonFile) > 0 {
|
||||
jFile, err := os.Create(jsonFile)
|
||||
jf, err := os.Create(jsonFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer jFile.Close()
|
||||
enc = json.NewEncoder(jFile)
|
||||
defer jf.Close()
|
||||
enc = json.NewEncoder(jf)
|
||||
} else {
|
||||
enc = json.NewEncoder(os.Stdout)
|
||||
}
|
||||
|
||||
if err := enc.Encode(slout); err != nil {
|
||||
if err := enc.Encode(syms); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
+103
-223
@@ -22,32 +22,17 @@ THE SOFTWARE.
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/apex/log"
|
||||
"github.com/blacktop/ipsw/internal/download"
|
||||
"github.com/blacktop/ipsw/internal/commands/extract"
|
||||
"github.com/blacktop/ipsw/internal/utils"
|
||||
"github.com/blacktop/ipsw/pkg/devicetree"
|
||||
"github.com/blacktop/ipsw/pkg/dyld"
|
||||
"github.com/blacktop/ipsw/pkg/img4"
|
||||
"github.com/blacktop/ipsw/pkg/info"
|
||||
"github.com/blacktop/ipsw/pkg/kernelcache"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func isURL(str string) bool {
|
||||
u, err := url.Parse(str)
|
||||
return err == nil && u.Scheme != "" && u.Host != ""
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(extractCmd)
|
||||
|
||||
@@ -57,7 +42,7 @@ func init() {
|
||||
extractCmd.Flags().BoolP("kernel", "k", false, "Extract kernelcache")
|
||||
extractCmd.Flags().BoolP("dyld", "d", false, "Extract dyld_shared_cache")
|
||||
extractCmd.Flags().BoolP("dtree", "t", false, "Extract DeviceTree")
|
||||
extractCmd.Flags().BoolP("dmg", "m", false, "Extract File System DMG file")
|
||||
extractCmd.Flags().StringP("dmg", "m", "", "Extract DMG file (app, sys, fs)")
|
||||
extractCmd.Flags().BoolP("iboot", "i", false, "Extract iBoot")
|
||||
extractCmd.Flags().BoolP("sep", "s", false, "Extract sep-firmware")
|
||||
extractCmd.Flags().BoolP("kbag", "b", false, "Extract Im4p Keybags")
|
||||
@@ -66,6 +51,13 @@ func init() {
|
||||
extractCmd.Flags().StringP("output", "o", "", "Folder to extract files to")
|
||||
extractCmd.Flags().Bool("flat", false, "Do NOT perserve directory structure when extracting")
|
||||
extractCmd.Flags().StringArrayP("dyld-arch", "a", []string{}, "dyld_shared_cache architecture to extract")
|
||||
extractCmd.RegisterFlagCompletionFunc("dmg", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
return []string{
|
||||
"app\tAppOS",
|
||||
"sys\tSystemOS",
|
||||
"fs\tFileSystem",
|
||||
}, cobra.ShellCompDirectiveDefault
|
||||
})
|
||||
|
||||
viper.BindPFlag("extract.proxy", extractCmd.Flags().Lookup("proxy"))
|
||||
viper.BindPFlag("extract.insecure", extractCmd.Flags().Lookup("insecure"))
|
||||
@@ -112,243 +104,131 @@ var extractCmd = &cobra.Command{
|
||||
return fmt.Errorf("invalid dyld_shared_cache architecture '%s' (must be: arm64, arm64e, x86_64 or x86_64h)", arch)
|
||||
}
|
||||
}
|
||||
} else if viper.GetBool("extract.dmg") {
|
||||
if !utils.StrSliceHas([]string{"app", "sys", "fs"}, viper.GetString("extract.dmg")) {
|
||||
return fmt.Errorf("invalid DMG type '%s' (must be: app, sys or fs)", viper.GetString("extract.dmg"))
|
||||
}
|
||||
}
|
||||
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get current working directory: %v", err)
|
||||
}
|
||||
|
||||
config := &extract.Config{
|
||||
IPSW: "",
|
||||
URL: "",
|
||||
Pattern: viper.GetString("extract.pattern"),
|
||||
Arches: viper.GetStringSlice("extract.dyld-arch"),
|
||||
Proxy: viper.GetString("extract.proxy"),
|
||||
Insecure: viper.GetBool("extract.insecure"),
|
||||
DMGs: false,
|
||||
DmgType: viper.GetString("extract.dmg"),
|
||||
Flatten: viper.GetBool("extract.flat"),
|
||||
Progress: true,
|
||||
Output: viper.GetString("extract.output"),
|
||||
}
|
||||
|
||||
if viper.GetBool("extract.remote") {
|
||||
remoteURL := args[0]
|
||||
config.URL = args[0]
|
||||
} else {
|
||||
config.IPSW = args[0]
|
||||
}
|
||||
|
||||
if !isURL(remoteURL) {
|
||||
log.Fatal("must supply valid URL when using the remote flag")
|
||||
}
|
||||
|
||||
// Get handle to remote IPSW zip
|
||||
zr, err := download.NewRemoteZipReader(remoteURL, &download.RemoteConfig{
|
||||
Proxy: viper.GetString("extract.proxy"),
|
||||
Insecure: viper.GetBool("extract.insecure"),
|
||||
})
|
||||
if viper.GetBool("extract.kernel") {
|
||||
log.Info("Extracting kernelcache")
|
||||
artifacts, err := extract.Kernelcache(config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to download remote zip: %v", err)
|
||||
return err
|
||||
}
|
||||
for _, artifact := range artifacts {
|
||||
utils.Indent(log.Info, 2)("Extracted " + strings.TrimPrefix(cwd, artifact))
|
||||
}
|
||||
}
|
||||
|
||||
i, err := info.ParseZipFiles(zr.File)
|
||||
if viper.GetBool("extract.dyld") {
|
||||
log.Info("Extracting dyld_shared_cache")
|
||||
artifacts, err := extract.DSC(config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse plists in remote zip: %v", err)
|
||||
return err
|
||||
}
|
||||
folder, err := i.GetFolder()
|
||||
if err != nil {
|
||||
log.Errorf("failed to get folder from remote zip metadata: %v", err)
|
||||
for _, artifact := range artifacts {
|
||||
utils.Indent(log.Info, 2)("Extracted " + strings.TrimPrefix(cwd, artifact))
|
||||
}
|
||||
destPath := filepath.Join(filepath.Clean(viper.GetString("extract.output")), folder)
|
||||
}
|
||||
|
||||
if viper.GetBool("extract.kernel") {
|
||||
log.Info("Extracting remote kernelcache")
|
||||
if err = kernelcache.RemoteParse(zr, destPath); err != nil {
|
||||
return fmt.Errorf("failed to extract kernelcache from remote IPSW: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if viper.GetBool("extract.dyld") {
|
||||
log.Info("Extracting remote dyld_shared_cache(s)")
|
||||
sysDMG, err := i.GetSystemOsDmg()
|
||||
if err != nil {
|
||||
return fmt.Errorf("only iOS16.x/macOS13.x supported: failed to get SystemOS DMG from remote zip metadata: %v", err)
|
||||
}
|
||||
if len(sysDMG) == 0 {
|
||||
return fmt.Errorf("only iOS16.x/macOS13.x supported: no SystemOS DMG found in remote zip metadata")
|
||||
}
|
||||
tmpDIR, err := os.MkdirTemp("", "ipsw_extract_remote_dyld")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create temporary directory to store SystemOS DMG: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDIR)
|
||||
if err := utils.RemoteUnzip(zr.File, regexp.MustCompile(fmt.Sprintf("^%s$", sysDMG)), tmpDIR, viper.GetBool("extract.flat"), true); err != nil {
|
||||
return fmt.Errorf("failed to extract SystemOS DMG from remote IPSW: %v", err)
|
||||
}
|
||||
if err := dyld.ExtractFromDMG(i, filepath.Join(tmpDIR, sysDMG), destPath, viper.GetStringSlice("extract.dyld-arch")); err != nil {
|
||||
return fmt.Errorf("failed to extract dyld_shared_cache(s) from remote IPSW: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if viper.GetBool("extract.dmg") {
|
||||
if viper.GetBool("extract.dmg") {
|
||||
config.DMGs = true
|
||||
if viper.GetBool("extract.remote") {
|
||||
log.Error("unable to extract File System DMG remotely (let the author know if this is something you want)")
|
||||
}
|
||||
|
||||
if viper.GetBool("extract.dtree") {
|
||||
log.Info("Extracting remote DeviceTree(s)")
|
||||
if err := utils.RemoteUnzip(zr.File, regexp.MustCompile(`.*DeviceTree.*im(3|4)p$`), destPath, viper.GetBool("extract.flat"), true); err != nil {
|
||||
return fmt.Errorf("failed to extract DeviceTree from remote IPSW: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if viper.GetBool("extract.iboot") {
|
||||
log.Info("Extracting remote iBoot(s)")
|
||||
if err := utils.RemoteUnzip(zr.File, regexp.MustCompile(`.*iBoot.*im4p$`), destPath, viper.GetBool("extract.flat"), true); err != nil {
|
||||
return fmt.Errorf("failed to extract iBoot from remote IPSW: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if viper.GetBool("extract.sep") {
|
||||
log.Info("Extracting sep-firmware(s)")
|
||||
if err := utils.RemoteUnzip(zr.File, regexp.MustCompile(`.*sep-firmware.*im4p$`), destPath, viper.GetBool("extract.flat"), true); err != nil {
|
||||
return fmt.Errorf("failed to extract SEPOS from remote IPSW: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if viper.GetBool("extract.kbag") {
|
||||
log.Info("Extracting im4p kbags")
|
||||
kbags, err := img4.ParseZipKeyBags(zr.File, i, viper.GetString("extract.pattern"))
|
||||
} else {
|
||||
log.Info("Extracting DMG")
|
||||
artifacts, err := extract.DMG(config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse im4p kbags: %v", err)
|
||||
return err
|
||||
}
|
||||
out, err := json.Marshal(kbags)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal im4p kbags: %v", err)
|
||||
}
|
||||
fmt.Println(string(out))
|
||||
os.Mkdir(destPath, 0770)
|
||||
if err := os.WriteFile(filepath.Join(destPath, "kbags.json"), out, 0660); err != nil {
|
||||
return fmt.Errorf("failed to write %s: %v", filepath.Join(destPath, "kbags.json"), err)
|
||||
for _, artifact := range artifacts {
|
||||
utils.Indent(log.Info, 2)("Extracted " + strings.TrimPrefix(cwd, artifact))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(viper.GetString("extract.pattern")) > 0 {
|
||||
log.Infof("Extracting files matching pattern %#v", viper.GetString("extract.pattern"))
|
||||
validRegex, err := regexp.Compile(viper.GetString("extract.pattern"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to compile regexp: %v", err)
|
||||
}
|
||||
if err := utils.RemoteUnzip(zr.File, validRegex, destPath, viper.GetBool("extract.flat"), true); err != nil {
|
||||
return fmt.Errorf("failed to extract files matching pattern in remote IPSW: %v", err)
|
||||
}
|
||||
}
|
||||
} else { // local IPSW/OTA
|
||||
ipswPath := filepath.Clean(args[0])
|
||||
|
||||
if _, err := os.Stat(ipswPath); os.IsNotExist(err) {
|
||||
return fmt.Errorf("file %s does not exist", ipswPath)
|
||||
}
|
||||
|
||||
i, err := info.Parse(ipswPath)
|
||||
if viper.GetBool("extract.dtree") {
|
||||
log.Info("Extracting DeviceTree")
|
||||
config.Pattern = `.*DeviceTree.*im(3|4)p$`
|
||||
artifacts, err := extract.Search(config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse plists in IPSW: %v", err)
|
||||
return err
|
||||
}
|
||||
for _, artifact := range artifacts {
|
||||
utils.Indent(log.Info, 2)("Extracted " + strings.TrimPrefix(cwd, artifact))
|
||||
}
|
||||
}
|
||||
|
||||
folder, err := i.GetFolder()
|
||||
if viper.GetBool("extract.iboot") {
|
||||
log.Info("Extracting iBoot")
|
||||
config.Pattern = `.*iBoot.*im4p$`
|
||||
artifacts, err := extract.Search(config)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get folder from zip metadata: %v", err)
|
||||
return err
|
||||
}
|
||||
destPath := filepath.Join(filepath.Clean(viper.GetString("extract.output")), folder)
|
||||
|
||||
if viper.GetBool("extract.kernel") {
|
||||
log.Info("Extracting kernelcaches")
|
||||
if err := kernelcache.Extract(ipswPath, destPath); err != nil {
|
||||
return fmt.Errorf("failed to extract kernelcaches from IPSW: %v", err)
|
||||
}
|
||||
for _, artifact := range artifacts {
|
||||
utils.Indent(log.Info, 2)("Extracted " + strings.TrimPrefix(cwd, artifact))
|
||||
}
|
||||
}
|
||||
|
||||
if viper.GetBool("extract.dyld") {
|
||||
log.Info("Extracting dyld_shared_cache")
|
||||
if err := dyld.Extract(ipswPath, destPath, viper.GetStringSlice("extract.dyld-arch")); err != nil {
|
||||
return fmt.Errorf("failed to extract dyld_shared_cache(s) from IPSW: %v", err)
|
||||
}
|
||||
if viper.GetBool("extract.sep") {
|
||||
log.Info("Extracting sep-firmware")
|
||||
config.Pattern = `.*sep-firmware.*im4p$`
|
||||
artifacts, err := extract.Search(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if viper.GetBool("extract.dtree") {
|
||||
log.Info("Extracting DeviceTrees")
|
||||
if err := devicetree.Extract(ipswPath, destPath); err != nil {
|
||||
return fmt.Errorf("failed to extract DeviceTrees from IPSW: %v", err)
|
||||
}
|
||||
for _, artifact := range artifacts {
|
||||
utils.Indent(log.Info, 2)("Extracted " + strings.TrimPrefix(cwd, artifact))
|
||||
}
|
||||
}
|
||||
|
||||
if viper.GetBool("extract.dmg") {
|
||||
fsDMG, err := i.GetFileSystemOsDmg()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to find filesystem DMG in IPSW: %v", err)
|
||||
}
|
||||
log.Info("Extracting File System DMG")
|
||||
if _, err := utils.Unzip(ipswPath, destPath, func(f *zip.File) bool {
|
||||
return strings.EqualFold(filepath.Base(f.Name), fsDMG)
|
||||
}); err != nil {
|
||||
return fmt.Errorf("failed extract %s from IPSW: %v", fsDMG, err)
|
||||
}
|
||||
log.Infof("Created %s", filepath.Join(destPath, fsDMG))
|
||||
if viper.GetBool("extract.kbag") {
|
||||
log.Info("Extracting im4p key bags")
|
||||
artifact, err := extract.Keybags(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
utils.Indent(log.Info, 2)("Extracted " + strings.TrimPrefix(cwd, artifact))
|
||||
}
|
||||
|
||||
if viper.GetBool("extract.iboot") {
|
||||
log.Info("Extracting iBoot")
|
||||
if _, err := utils.Unzip(ipswPath, destPath, func(f *zip.File) bool {
|
||||
var validIBoot = regexp.MustCompile(`.*iBoot.*im4p$`)
|
||||
return validIBoot.MatchString(f.Name)
|
||||
}); err != nil {
|
||||
return fmt.Errorf("failed extract iBoot from IPSW: %v", err)
|
||||
}
|
||||
if len(viper.GetString("extract.pattern")) > 0 {
|
||||
log.Infof("Extracting files matching pattern %#v", viper.GetString("extract.pattern"))
|
||||
if viper.GetBool("extract.files") {
|
||||
config.DMGs = true
|
||||
}
|
||||
|
||||
if viper.GetBool("extract.sep") {
|
||||
log.Info("Extracting sep-firmwares")
|
||||
if _, err := utils.Unzip(ipswPath, destPath, func(f *zip.File) bool {
|
||||
var validSEP = regexp.MustCompile(`.*sep-firmware.*im4p$`)
|
||||
return validSEP.MatchString(f.Name)
|
||||
}); err != nil {
|
||||
return fmt.Errorf("failed to extract sep-firmwares from IPSW: %v", err)
|
||||
}
|
||||
artifacts, err := extract.Search(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if viper.GetBool("extract.kbag") {
|
||||
log.Info("Extracting im4p kbags")
|
||||
zr, err := zip.OpenReader(ipswPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open zip: %v", err)
|
||||
}
|
||||
defer zr.Close()
|
||||
kbags, err := img4.ParseZipKeyBags(zr.File, i, viper.GetString("extract.pattern"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse im4p kbags: %v", err)
|
||||
}
|
||||
out, err := json.Marshal(kbags)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal im4p kbags: %v", err)
|
||||
}
|
||||
fmt.Println(string(out))
|
||||
os.Mkdir(destPath, 0770)
|
||||
if err := os.WriteFile(filepath.Join(destPath, "kbags.json"), out, 0660); err != nil {
|
||||
return fmt.Errorf("failed to write %s: %v", filepath.Join(destPath, "kbags.json"), err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(viper.GetString("extract.pattern")) > 0 {
|
||||
log.Infof("Extracting files matching pattern %#v", viper.GetString("extract.pattern"))
|
||||
patternRE, err := regexp.Compile(viper.GetString("extract.pattern"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to compile regexp: %v", err)
|
||||
}
|
||||
|
||||
if viper.GetBool("extract.files") { // SEARCH THE DMGs
|
||||
if appOS, err := i.GetAppOsDmg(); err == nil {
|
||||
if err := utils.ExtractFromDMG(ipswPath, appOS, destPath, patternRE); err != nil {
|
||||
return fmt.Errorf("failed to extract files from AppOS %s: %v", appOS, err)
|
||||
}
|
||||
}
|
||||
if systemOS, err := i.GetSystemOsDmg(); err == nil {
|
||||
if err := utils.ExtractFromDMG(ipswPath, systemOS, destPath, patternRE); err != nil {
|
||||
return fmt.Errorf("failed to extract files from SystemOS %s: %v", systemOS, err)
|
||||
}
|
||||
}
|
||||
if fsOS, err := i.GetFileSystemOsDmg(); err == nil {
|
||||
if err := utils.ExtractFromDMG(ipswPath, fsOS, destPath, patternRE); err != nil {
|
||||
return fmt.Errorf("failed to extract files from filesystem %s: %v", fsOS, err)
|
||||
}
|
||||
}
|
||||
} else { // SEARCH THE ZIP
|
||||
zr, err := zip.OpenReader(ipswPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open IPSW: %v", err)
|
||||
}
|
||||
defer zr.Close()
|
||||
if err := utils.RemoteUnzip(zr.File, patternRE, destPath, viper.GetBool("extract.flat"), false); err != nil {
|
||||
return fmt.Errorf("failed to extract files matching pattern: %v", err)
|
||||
}
|
||||
}
|
||||
for _, artifact := range artifacts {
|
||||
utils.Indent(log.Info, 2)("Extracted " + strings.TrimPrefix(cwd, artifact))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+9
-73
@@ -1,5 +1,3 @@
|
||||
//go:build darwin
|
||||
|
||||
/*
|
||||
Copyright © 2018-2023 blacktop
|
||||
|
||||
@@ -24,19 +22,15 @@ THE SOFTWARE.
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/apex/log"
|
||||
"github.com/blacktop/ipsw/internal/commands/mount"
|
||||
"github.com/blacktop/ipsw/internal/utils"
|
||||
"github.com/blacktop/ipsw/pkg/info"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -66,82 +60,24 @@ var mountCmd = &cobra.Command{
|
||||
log.SetLevel(log.DebugLevel)
|
||||
}
|
||||
|
||||
ipswPath := filepath.Clean(args[1])
|
||||
|
||||
i, err := info.Parse(ipswPath)
|
||||
mctx, err := mount.DmgInIPSW(args[1], args[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse IPSW: %v", err)
|
||||
return fmt.Errorf("failed to mount %s DMG: %v", args[0], err)
|
||||
}
|
||||
|
||||
var dmgPath string
|
||||
|
||||
switch args[0] {
|
||||
case "fs":
|
||||
dmgPath, err = i.GetFileSystemOsDmg()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get filesystem DMG: %v", err)
|
||||
}
|
||||
log.Info("Found Filesystem DMG")
|
||||
case "sys":
|
||||
dmgPath, err = i.GetSystemOsDmg()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get SystemOS DMG: %v", err)
|
||||
}
|
||||
log.Info("Found SystemOS DMG")
|
||||
case "app":
|
||||
dmgPath, err = i.GetAppOsDmg()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get AppOS DMG: %v", err)
|
||||
}
|
||||
log.Info("Found AppOS DMG")
|
||||
default:
|
||||
return fmt.Errorf("invalid subcommand: %s; must be one of: '%s'", args[0], strings.Join(mountCmdSubCmds, "', '"))
|
||||
}
|
||||
|
||||
// check if filesystem DMG already exists (due to previous mount command)
|
||||
if _, err := os.Stat(dmgPath); os.IsNotExist(err) {
|
||||
// extract filesystem DMG
|
||||
dmgs, err := utils.Unzip(ipswPath, "", func(f *zip.File) bool {
|
||||
return strings.EqualFold(filepath.Base(f.Name), dmgPath)
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to extract %s from IPSW: %v", dmgPath, err)
|
||||
}
|
||||
if len(dmgs) == 0 {
|
||||
return fmt.Errorf("failed to find %s in IPSW", dmgPath)
|
||||
}
|
||||
defer os.Remove(dmgs[0])
|
||||
if mctx.AlreadyMounted {
|
||||
log.Infof("%s DMG already mounted at %s", args[0], mctx.MountPoint)
|
||||
} else {
|
||||
utils.Indent(log.Debug, 2)(fmt.Sprintf("Found extracted %s", dmgPath))
|
||||
}
|
||||
|
||||
// mount filesystem DMG
|
||||
utils.Indent(log.Info, 2)(fmt.Sprintf("Mounting %s", dmgPath))
|
||||
mountPoint, alreadyMounted, err := utils.MountFS(dmgPath)
|
||||
if err != nil {
|
||||
if !errors.Is(err, utils.ErrMountResourceBusy) {
|
||||
return fmt.Errorf("failed to mount DMG: %v", err)
|
||||
}
|
||||
}
|
||||
if alreadyMounted {
|
||||
utils.Indent(log.Info, 3)(fmt.Sprintf("%s already mounted", dmgPath))
|
||||
} else {
|
||||
defer func() {
|
||||
utils.Indent(log.Info, 2)(fmt.Sprintf("Unmounting %s", dmgPath))
|
||||
if err := utils.Retry(3, 2*time.Second, func() error {
|
||||
return utils.Unmount(mountPoint, false)
|
||||
}); err != nil {
|
||||
log.Errorf("failed to unmount %s at %s: %v", dmgPath, mountPoint, err)
|
||||
}
|
||||
}()
|
||||
log.Infof("Mounted %s DMG %s", args[0], filepath.Base(mctx.DmgPath))
|
||||
}
|
||||
|
||||
// block until user hits ctrl-c
|
||||
done := make(chan os.Signal, 1)
|
||||
signal.Notify(done, syscall.SIGINT, syscall.SIGTERM)
|
||||
utils.Indent(log.Info, 3)(fmt.Sprintf("Press Ctrl+C to unmount '%s' ...", mountPoint))
|
||||
utils.Indent(log.Info, 2)(fmt.Sprintf("Press Ctrl+C to unmount '%s' ...", mctx.MountPoint))
|
||||
<-done
|
||||
|
||||
return nil
|
||||
utils.Indent(log.Info, 2)(fmt.Sprintf("Unmounting %s", mctx.MountPoint))
|
||||
return mctx.Unmount()
|
||||
},
|
||||
}
|
||||
|
||||
@@ -79,14 +79,8 @@ var pongoCmd = &cobra.Command{
|
||||
var kbags *img4.KeyBags
|
||||
|
||||
if viper.GetBool("pongo.remote") {
|
||||
remoteURL := args[0]
|
||||
|
||||
if !isURL(remoteURL) {
|
||||
log.Fatal("must supply valid URL when using the remote flag")
|
||||
}
|
||||
|
||||
// Get handle to remote IPSW zip
|
||||
zr, err := download.NewRemoteZipReader(remoteURL, &download.RemoteConfig{
|
||||
zr, err := download.NewRemoteZipReader(args[0], &download.RemoteConfig{
|
||||
Proxy: viper.GetString("pongo.proxy"),
|
||||
Insecure: viper.GetBool("pongo.insecure"),
|
||||
})
|
||||
@@ -112,7 +106,7 @@ var pongoCmd = &cobra.Command{
|
||||
|
||||
if viper.GetBool("pongo.decrypt") {
|
||||
for _, kbag := range kbags.Files {
|
||||
if err := utils.RemoteUnzip(zr.File, regexp.MustCompile(kbag.Name), destPath, true, true); err != nil {
|
||||
if _, err := utils.SearchZip(zr.File, regexp.MustCompile(kbag.Name), destPath, true, true); err != nil {
|
||||
return fmt.Errorf("failed to extract files matching pattern in remote IPSW: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -148,7 +142,7 @@ var pongoCmd = &cobra.Command{
|
||||
|
||||
if viper.GetBool("pongo.decrypt") {
|
||||
for _, kbag := range kbags.Files {
|
||||
if err := utils.RemoteUnzip(zr.File, regexp.MustCompile(fmt.Sprintf(".*%s$", kbag.Name)), destPath, true, false); err != nil {
|
||||
if _, err := utils.SearchZip(zr.File, regexp.MustCompile(fmt.Sprintf(".*%s$", kbag.Name)), destPath, true, false); err != nil {
|
||||
return fmt.Errorf("failed to extract files matching pattern in remote IPSW: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ func init() {
|
||||
// will be global for your application.
|
||||
|
||||
// Flags
|
||||
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.ipsw/config.yaml)")
|
||||
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.config/ipsw/config.yaml)")
|
||||
rootCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "V", false, "verbose output")
|
||||
rootCmd.PersistentFlags().BoolVar(&Color, "color", false, "colorize output")
|
||||
rootCmd.PersistentFlags().String("diff-tool", "", "git diff tool (for --diff commands)")
|
||||
@@ -109,7 +109,7 @@ func initConfig() {
|
||||
cobra.CheckErr(err)
|
||||
|
||||
// Search config in home directory with name ".ipsw" (without extension).
|
||||
viper.AddConfigPath(filepath.Join(home, ".ipsw"))
|
||||
viper.AddConfigPath(filepath.Join(home, ".config", "ipsw"))
|
||||
viper.SetConfigType("yaml")
|
||||
viper.SetConfigName("config")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright © 2023 blacktop
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
Copyright © 2023 blacktop
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/apex/log"
|
||||
clihander "github.com/apex/log/handlers/cli"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var cfgFile string
|
||||
|
||||
// rootCmd represents the base command when called without any subcommands
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "ipswd",
|
||||
Short: "ipsw daemon",
|
||||
}
|
||||
|
||||
// Execute adds all child commands to the root command and sets flags appropriately.
|
||||
// This is called by main.main(). It only needs to happen once to the rootCmd.
|
||||
func Execute() {
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
log.Error(err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
log.SetHandler(clihander.Default)
|
||||
|
||||
cobra.OnInitialize(initConfig)
|
||||
// Flags
|
||||
rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "config file (default is $HOME/.config/ipsw/config.yaml)")
|
||||
// Settings
|
||||
rootCmd.CompletionOptions.HiddenDefaultCmd = true
|
||||
}
|
||||
|
||||
// initConfig reads in config file and ENV variables if set.
|
||||
func initConfig() {
|
||||
if cfgFile != "" {
|
||||
// Use config file from the flag.
|
||||
viper.SetConfigFile(cfgFile)
|
||||
} else {
|
||||
// Find home directory.
|
||||
home, err := os.UserHomeDir()
|
||||
cobra.CheckErr(err)
|
||||
|
||||
// Search config in home directory with name ".ipsw" (without extension).
|
||||
viper.AddConfigPath(filepath.Join(home, ".config", "ipsw"))
|
||||
viper.SetConfigType("yaml")
|
||||
viper.SetConfigName("config")
|
||||
}
|
||||
|
||||
viper.SetEnvPrefix("ipsw")
|
||||
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_", ".", "_"))
|
||||
viper.AutomaticEnv()
|
||||
|
||||
// If a config file is found, read it in.
|
||||
if err := viper.ReadInConfig(); err == nil {
|
||||
fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
Copyright © 2023 blacktop
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/blacktop/ipsw/internal/daemon"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(startCmd)
|
||||
|
||||
startCmd.Flags().BoolP("debug", "d", false, "Debug mode")
|
||||
viper.BindPFlag("start.debug", startCmd.Flags().Lookup("debug"))
|
||||
}
|
||||
|
||||
// startCmd represents the start command
|
||||
var startCmd = &cobra.Command{
|
||||
Use: "start",
|
||||
Short: "Start the ipswd daemon",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return daemon.NewDaemon().Start()
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
Copyright © 2023 blacktop
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(statusCmd)
|
||||
}
|
||||
|
||||
// statusCmd represents the status command
|
||||
var statusCmd = &cobra.Command{
|
||||
Use: "status",
|
||||
Short: "Get the ipswd daemon status",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
panic("not implemented")
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
Copyright © 2023 blacktop
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/blacktop/ipsw/internal/daemon"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(stopCmd)
|
||||
}
|
||||
|
||||
// stopCmd represents the stop command
|
||||
var stopCmd = &cobra.Command{
|
||||
Use: "stop",
|
||||
Short: "Stop the ipswd daemon",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return daemon.NewDaemon().Stop()
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
Copyright © 2023 blacktop
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/blacktop/ipsw/api/types"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(versionCmd)
|
||||
}
|
||||
|
||||
// versionCmd represents the version command
|
||||
var versionCmd = &cobra.Command{
|
||||
Use: "version",
|
||||
Aliases: []string{"v"},
|
||||
Short: "Print the version number of ipswd",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Printf("Version: %s, BuildTime: %s\n", strings.TrimSpace(types.BuildVersion), strings.TrimSpace(types.BuildTime))
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
Copyright © 2023 blacktop
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
package main
|
||||
|
||||
import "github.com/blacktop/ipsw/cmd/ipswd/cmd"
|
||||
|
||||
func main() {
|
||||
cmd.Execute()
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
# This is an example config.yml file with some sensible defaults.
|
||||
# Make sure to check the documentation at https://blacktop.github.io/ipsw
|
||||
daemon:
|
||||
socket: /tmp/ipsw.sock
|
||||
debug: false
|
||||
# The lines beneath this are called `modelines`. See `:help modeline`
|
||||
# Feel free to remove those if you don't want/use them.
|
||||
# yaml-language-server: $schema=https://blacktop.github.io/ipsw/static/schema.json
|
||||
# vim: set ts=2 sw=2 tw=0 fo=cnqoj
|
||||
@@ -23,12 +23,13 @@ require (
|
||||
github.com/frida/frida-go v0.6.2
|
||||
github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa
|
||||
github.com/gen2brain/beeep v0.0.0-20230307103607-6e717729cb4f
|
||||
github.com/gin-gonic/gin v1.9.0
|
||||
github.com/glebarez/sqlite v1.7.0
|
||||
github.com/gocolly/colly/v2 v2.1.0
|
||||
github.com/gomarkdown/markdown v0.0.0-20230322041520-c84983bdbf2a
|
||||
github.com/google/gousb v1.1.2
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/hashicorp/go-version v1.6.0
|
||||
github.com/jinzhu/gorm v1.9.16
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/mitchellh/mapstructure v1.5.0
|
||||
github.com/olekukonko/tablewriter v0.0.5
|
||||
@@ -50,6 +51,8 @@ require (
|
||||
golang.org/x/sys v0.7.0
|
||||
golang.org/x/term v0.7.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
gorm.io/driver/postgres v1.5.0
|
||||
gorm.io/gorm v1.24.7-0.20230306060331-85eaf9eeda11
|
||||
)
|
||||
|
||||
// TODO: remove this once https://github.com/spf13/cast/pull/155 is merged
|
||||
@@ -70,6 +73,8 @@ require (
|
||||
github.com/antchfx/htmlquery v1.3.0 // indirect
|
||||
github.com/antchfx/xmlquery v1.3.15 // indirect
|
||||
github.com/antchfx/xpath v1.2.4 // indirect
|
||||
github.com/bytedance/sonic v1.8.7 // indirect
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||
github.com/creack/pty v1.1.18 // indirect
|
||||
github.com/danieljoos/wincred v1.1.2 // indirect
|
||||
@@ -79,8 +84,14 @@ require (
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/dvsekhvalnov/jose2go v1.5.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/glebarez/go-sqlite v1.21.1 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.12.0 // indirect
|
||||
github.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4 // indirect
|
||||
github.com/gobwas/glob v0.2.3 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect
|
||||
github.com/godbus/dbus/v5 v5.1.0 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
@@ -90,22 +101,31 @@ require (
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/hinshun/vt10x v0.0.0-20220301184237-5011da428d02 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||
github.com/jackc/pgx/v5 v5.3.1 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/kennygrant/sanitize v1.2.4 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
|
||||
github.com/kr/pretty v0.3.1 // indirect
|
||||
github.com/leodido/go-urn v1.2.3 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.18 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.14 // indirect
|
||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
||||
github.com/moby/term v0.0.0-20221205130635-1aeaba878587 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/mtibben/percent v0.2.1 // indirect
|
||||
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.7 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/rivo/uniseg v0.4.4 // indirect
|
||||
github.com/rogpeppe/go-internal v1.10.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
@@ -117,12 +137,18 @@ require (
|
||||
github.com/subosito/gotenv v1.4.2 // indirect
|
||||
github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af // indirect
|
||||
github.com/temoto/robotstxt v1.1.2 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||
golang.org/x/arch v0.3.0 // indirect
|
||||
golang.org/x/mod v0.10.0 // indirect
|
||||
golang.org/x/text v0.9.0 // indirect
|
||||
golang.org/x/tools v0.8.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/protobuf v1.30.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gotest.tools/v3 v3.4.0 // indirect
|
||||
modernc.org/libc v1.22.3 // indirect
|
||||
modernc.org/mathutil v1.5.0 // indirect
|
||||
modernc.org/memory v1.5.0 // indirect
|
||||
modernc.org/sqlite v1.21.1 // indirect
|
||||
)
|
||||
|
||||
@@ -97,9 +97,15 @@ github.com/blacktop/lzss v0.1.1 h1:ADOUgVH3hq2miBfaxhV1GgY97wNSv5fBiXRnUVe8QSg=
|
||||
github.com/blacktop/lzss v0.1.1/go.mod h1:eWPx0Tq21QndictvoAb20bFvvhDDbZ3mkcZ/mDHkwBk=
|
||||
github.com/blacktop/ranger v1.0.3 h1:uIWlR8chK6AtNNPZz67+cwibOe8T5XnYyQfkk04ueZA=
|
||||
github.com/blacktop/ranger v1.0.3/go.mod h1:2KXlAasHzrHMkXg1+/87hT5ISdQjxlcjbmZFZD69zas=
|
||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||
github.com/bytedance/sonic v1.8.7 h1:d3sry5vGgVq/OpgozRUNP6xBsSo0mtNdwliApw+SAMQ=
|
||||
github.com/bytedance/sonic v1.8.7/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
||||
github.com/caarlos0/ctrlc v1.2.0 h1:AtbThhmbeYx1WW3WXdWrd94EHKi+0NPRGS4/4pzrjwk=
|
||||
github.com/caarlos0/ctrlc v1.2.0/go.mod h1:n3gDlSjsXZ7rbD9/RprIR040b7oaLfNStikPd4gFago=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
@@ -118,8 +124,6 @@ github.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnG
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
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/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd h1:83Wprp6ROGeiHFAP8WJdI2RoxALQYgdllERc3N5N2DM=
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
|
||||
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||
github.com/dlclark/regexp2 v1.9.0 h1:pTK/l/3qYIKaRXuHnEnIf7Y5NxfRPfpb7dis6/gdlVI=
|
||||
github.com/dlclark/regexp2 v1.9.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
@@ -143,8 +147,6 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
|
||||
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y=
|
||||
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
|
||||
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
|
||||
@@ -158,16 +160,31 @@ github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa h1:RDBNVkRviHZtvD
|
||||
github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA=
|
||||
github.com/gen2brain/beeep v0.0.0-20230307103607-6e717729cb4f h1:oRm7Hy2dQWfHgOuOWRaYZf+kZcWJst7fxAlq+yjdLss=
|
||||
github.com/gen2brain/beeep v0.0.0-20230307103607-6e717729cb4f/go.mod h1:0W7dI87PvXJ1Sjs0QPvWXKcQmNERY77e8l7GFhZB/s4=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8=
|
||||
github.com/gin-gonic/gin v1.9.0/go.mod h1:W1Me9+hsUSyj3CePGrd1/QrKJMSJ1Tu/0hFEH89961k=
|
||||
github.com/glebarez/go-sqlite v1.21.1 h1:7MZyUPh2XTrHS7xNEHQbrhfMZuPSzhkm2A1qgg0y5NY=
|
||||
github.com/glebarez/go-sqlite v1.21.1/go.mod h1:ISs8MF6yk5cL4n/43rSOmVMGJJjHYr7L2MbZZ5Q4E2E=
|
||||
github.com/glebarez/sqlite v1.7.0 h1:A7Xj/KN2Lvie4Z4rrgQHY8MsbebX3NyWsL3n2i82MVI=
|
||||
github.com/glebarez/sqlite v1.7.0/go.mod h1:PkeevrRlF/1BhQBCnzcMWzgrIk7IOop+qS2jUYLfHhk=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.12.0 h1:E4gtWgxWxp8YSxExrQFv5BpCahla0PVF2oTTEYaWQGI=
|
||||
github.com/go-playground/validator/v10 v10.12.0/go.mod h1:hCAPuzYvKdP33pxWa+2+6AIKXEKqjIUyqsNCtbsSJrA=
|
||||
github.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4 h1:qZNfIGkIANxGv/OqtnntR4DfOY2+BgwR60cAcu/i3SE=
|
||||
github.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4/go.mod h1:kW3HQ4UdaAyrUCSSDR4xUzBKW6O2iA4uHhk7AtyYp10=
|
||||
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/gocolly/colly v1.2.0/go.mod h1:Hof5T3ZswNVsOHYmba1u03W65HDWgpV5HifSuueE0EA=
|
||||
github.com/gocolly/colly/v2 v2.1.0 h1:k0DuZkDoCsx51bKpRJNEmcxcp+W5N8ziuwGaSDuFoGs=
|
||||
github.com/gocolly/colly/v2 v2.1.0/go.mod h1:I2MuhsLjQ+Ex+IzK3afNS8/1qP3AedHOusRPcRdC5o0=
|
||||
@@ -177,8 +194,6 @@ github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
||||
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
|
||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
@@ -224,6 +239,7 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gousb v1.1.2 h1:1BwarNB3inFTFhPgUEfah4hwOPuDz/49I0uX8XNginU=
|
||||
github.com/google/gousb v1.1.2/go.mod h1:GGWUkK0gAXDzxhwrzetW592aOmkkqSGcj5KLEgmCVUg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
@@ -239,6 +255,7 @@ github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hf
|
||||
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
@@ -263,16 +280,24 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.3.0/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8=
|
||||
github.com/jackc/pgx/v5 v5.3.1 h1:Fcr8QJ1ZeLi5zsPZqQeUZhNhxfkkKBOgJuYkJHoBOtU=
|
||||
github.com/jackc/pgx/v5 v5.3.1/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8=
|
||||
github.com/jackc/puddle/v2 v2.2.0/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/jawher/mow.cli v1.1.0/go.mod h1:aNaQlc7ozF3vw6IJ2dHjp2ZFiA4ozMIYY6PyuRJwlUg=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jinzhu/gorm v1.9.16 h1:+IyIjPEABKRpsu/F8OvDPy9fyQlgsg2luMV2ZIH5i5o=
|
||||
github.com/jinzhu/gorm v1.9.16/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.0.1 h1:HjfetcXq097iXP0uoPCdnM4Efp5/9MsM0/M+XOTeR3M=
|
||||
github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7/go.mod h1:2iMrUgbbvHEiQClaW2NsSzMyGHqN+rDFqY705q49KG0=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
||||
@@ -281,19 +306,23 @@ github.com/kennygrant/sanitize v1.2.4 h1:gN25/otpP5vAsO2djbMhF/LQX6R7+O1TB4yv8Nz
|
||||
github.com/kennygrant/sanitize v1.2.4/go.mod h1:LGsjYYtgxbetdg5owWB2mpgUL6e2nfw2eObZ0u0qvak=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
|
||||
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4=
|
||||
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/leodido/go-urn v1.2.3 h1:6BE2vPT0lqoz3fmOesHZiaiFh7889ssCo2GMvLCfiuA=
|
||||
github.com/leodido/go-urn v1.2.3/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
||||
@@ -309,9 +338,6 @@ github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m
|
||||
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
|
||||
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
|
||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
|
||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||
@@ -321,6 +347,11 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/moby/term v0.0.0-20221205130635-1aeaba878587 h1:HfkjXDfhgVaN5rmueG8cL8KKeFNecRCXFhaJ2qZ5SKA=
|
||||
github.com/moby/term v0.0.0-20221205130635-1aeaba878587/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs=
|
||||
@@ -346,11 +377,15 @@ github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qR
|
||||
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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
|
||||
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rogpeppe/fastuuid v1.1.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||
@@ -392,8 +427,9 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8=
|
||||
github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
|
||||
github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af h1:6yITBqGTE2lEeTPG04SN9W+iWHCRyHqlVYILiSXziwk=
|
||||
@@ -408,6 +444,10 @@ github.com/tj/go-buffer v1.1.0/go.mod h1:iyiJpfFcR2B9sXu7KvjbT9fpM4mOelRSDTbntVj
|
||||
github.com/tj/go-elastic v0.0.0-20171221160941-36157cbbebc2/go.mod h1:WjeM0Oo1eNAjXGDx2yma7uG2XoyRZTq1uv3M/o7imD0=
|
||||
github.com/tj/go-kinesis v0.0.0-20171128231115-08b17f58cb1b/go.mod h1:/yhzCV0xPfx6jb1bBgRFjl5lytqVqZXEaeqWP8lTEao=
|
||||
github.com/tj/go-spin v1.1.0/go.mod h1:Mg1mzmePZm4dva8Qz60H2lHwmJ2loum4VIrLgVnKwh4=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
||||
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8=
|
||||
github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/unicorn-engine/unicorn v0.0.0-20230207094436-7b8c63dfe650 h1:O8wGQwIFVKk04THvsqbrMi9rXfJwxRU1Bf04NBWvdhI=
|
||||
@@ -427,17 +467,19 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
|
||||
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
||||
golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
|
||||
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
@@ -515,6 +557,7 @@ golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qx
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
|
||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
@@ -583,6 +626,7 @@ golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@@ -783,6 +827,10 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
|
||||
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/postgres v1.5.0 h1:u2FXTy14l45qc3UeCJ7QaAXZmZfDDv0YrthvmRq1l0U=
|
||||
gorm.io/driver/postgres v1.5.0/go.mod h1:FUZXzO+5Uqg5zzwzv4KK49R8lvGIyscBOqYrtI1Ce9A=
|
||||
gorm.io/gorm v1.24.7-0.20230306060331-85eaf9eeda11 h1:9qNbmu21nNThCNnF5i2R3kw2aL27U8ZwbzccNjOmW0g=
|
||||
gorm.io/gorm v1.24.7-0.20230306060331-85eaf9eeda11/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||
gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o=
|
||||
gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
@@ -792,6 +840,15 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
modernc.org/libc v1.22.3 h1:D/g6O5ftAfavceqlLOFwaZuA5KYafKwmr30A6iSqoyY=
|
||||
modernc.org/libc v1.22.3/go.mod h1:MQrloYP209xa2zHome2a8HLiLm6k0UT8CoHpV74tOFw=
|
||||
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
|
||||
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
|
||||
modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
|
||||
modernc.org/sqlite v1.21.1 h1:GyDFqNnESLOhwwDRaHGdp2jKLDzpyT/rNLglX3ZkMSU=
|
||||
modernc.org/sqlite v1.21.1/go.mod h1:XwQ0wZPIh1iKb5mkvCJ3szzbhk+tykC8ZWqTRTgYRwI=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
[Unit]
|
||||
Description=ipsw daemon
|
||||
Documentation=https://blacktop.github.io/ipsw
|
||||
After=network-online.target ipsw.socket
|
||||
Wants=network-online.target
|
||||
Requires=ipsw.socket
|
||||
|
||||
[Service]
|
||||
Type=notify
|
||||
ExecStart=/usr/bin/ipswd start
|
||||
ExecStop=/usr/bin/ipswd stop
|
||||
ExecReload=/bin/kill -s HUP $MAINPID
|
||||
TimeoutStartSec=0
|
||||
RestartSec=2
|
||||
Restart=always
|
||||
|
||||
# Note that StartLimit* options were moved from "Service" to "Unit" in systemd 229.
|
||||
# Both the old, and new location are accepted by systemd 229 and up, so using the old location
|
||||
# to make them work for either version of systemd.
|
||||
StartLimitBurst=3
|
||||
|
||||
# Note that StartLimitInterval was renamed to StartLimitIntervalSec in systemd 230.
|
||||
# Both the old, and new name are accepted by systemd 230 and up, so using the old name to make
|
||||
# this option work for either version of systemd.
|
||||
StartLimitInterval=60s
|
||||
|
||||
# Having non-zero Limit*s causes performance problems due to accounting overhead
|
||||
# in the kernel. We recommend using cgroups to do container-local accounting.
|
||||
LimitNOFILE=infinity
|
||||
LimitNPROC=infinity
|
||||
LimitCORE=infinity
|
||||
|
||||
# Comment TasksMax if your systemd version does not support it.
|
||||
# Only systemd 226 and above support this option.
|
||||
TasksMax=infinity
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -0,0 +1,13 @@
|
||||
[Unit]
|
||||
Description=ipswd Socket for the API
|
||||
|
||||
[Socket]
|
||||
# If /var/run is not implemented as a symlink to /run, you may need to
|
||||
# specify ListenStream=/var/run/ipsw.sock instead.
|
||||
ListenStream=/run/ipsw.sock
|
||||
SocketMode=0660
|
||||
SocketUser=root
|
||||
SocketGroup=ipsw
|
||||
|
||||
[Install]
|
||||
WantedBy=sockets.target
|
||||
@@ -0,0 +1,120 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -o errexit
|
||||
set -o nounset
|
||||
set -o pipefail
|
||||
if [[ "${TRACE-0}" == "1" ]]; then
|
||||
set -o xtrace
|
||||
fi
|
||||
|
||||
# utility functions
|
||||
INFO() {
|
||||
/bin/echo -e "\e[104m\e[97m[INFO]\e[49m\e[39m $@"
|
||||
}
|
||||
|
||||
WARNING() {
|
||||
/bin/echo >&2 -e "\e[101m\e[97m[WARNING]\e[49m\e[39m $@"
|
||||
}
|
||||
|
||||
ERROR() {
|
||||
/bin/echo >&2 -e "\e[101m\e[97m[ERROR]\e[49m\e[39m $@"
|
||||
}
|
||||
|
||||
# constants
|
||||
SYSTEMD_UNIT="ipsw.service"
|
||||
# global vars
|
||||
BIN=""
|
||||
SYSTEMD=""
|
||||
CFG_DIR=""
|
||||
XDG_RUNTIME_DIR_CREATED=""
|
||||
|
||||
# run checks and also initialize global vars
|
||||
init() {
|
||||
# OS verification: Linux only
|
||||
case "$(uname)" in
|
||||
Linux) ;;
|
||||
|
||||
*)
|
||||
ERROR "ipswd cannot be installed on $(uname)"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
# set SYSTEMD
|
||||
if systemctl --user show-environment >/dev/null 2>&1; then
|
||||
SYSTEMD=1
|
||||
fi
|
||||
# HOME verification
|
||||
if [ -z "${HOME:-}" ] || [ ! -d "$HOME" ]; then
|
||||
ERROR "HOME needs to be set"
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -w "$HOME" ]; then
|
||||
ERROR "HOME needs to be writable"
|
||||
exit 1
|
||||
fi
|
||||
# set CFG_DIR
|
||||
CFG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}"
|
||||
# Validate XDG_RUNTIME_DIR and set XDG_RUNTIME_DIR_CREATED
|
||||
if [ -z "${XDG_RUNTIME_DIR:-}" ] || [ ! -w "$XDG_RUNTIME_DIR" ]; then
|
||||
if [ -n "$SYSTEMD" ]; then
|
||||
ERROR "Aborting because systemd was detected but XDG_RUNTIME_DIR (\"$XDG_RUNTIME_DIR\") is not set, does not exist, or is not writable"
|
||||
ERROR "Hint: this could happen if you changed users with 'su' or 'sudo'. To work around this:"
|
||||
ERROR "- try again by first running with root privileges 'loginctl enable-linger <user>' where <user> is the unprivileged user and export XDG_RUNTIME_DIR to the value of RuntimePath as shown by 'loginctl show-user <user>'"
|
||||
ERROR "- or simply log back in as the desired unprivileged user (ssh works for remote machines, machinectl shell works for local machines)"
|
||||
exit 1
|
||||
fi
|
||||
export XDG_RUNTIME_DIR="$HOME/.docker/run"
|
||||
mkdir -p -m 700 "$XDG_RUNTIME_DIR"
|
||||
XDG_RUNTIME_DIR_CREATED=1
|
||||
fi
|
||||
}
|
||||
|
||||
show_systemd_error() {
|
||||
n="20"
|
||||
ERROR "Failed to start ${SYSTEMD_UNIT}. Run \`journalctl -n ${n} --no-pager --user --unit ${SYSTEMD_UNIT}\` to show the error log."
|
||||
}
|
||||
|
||||
# install (systemd)
|
||||
install_systemd() {
|
||||
mkdir -p "${CFG_DIR}/systemd/user"
|
||||
unit_file="${CFG_DIR}/systemd/user/${SYSTEMD_UNIT}"
|
||||
if [ -f "${unit_file}" ]; then
|
||||
WARNING "File already exists, skipping: ${unit_file}"
|
||||
else
|
||||
systemctl --user daemon-reload
|
||||
fi
|
||||
if ! systemctl --user --no-pager status "${SYSTEMD_UNIT}" >/dev/null 2>&1; then
|
||||
INFO "starting systemd service ${SYSTEMD_UNIT}"
|
||||
(
|
||||
set -x
|
||||
if ! systemctl --user start "${SYSTEMD_UNIT}"; then
|
||||
set +x
|
||||
show_systemd_error
|
||||
exit 1
|
||||
fi
|
||||
sleep 3
|
||||
)
|
||||
fi
|
||||
(
|
||||
set -x
|
||||
if ! systemctl --user --no-pager --full status "${SYSTEMD_UNIT}"; then
|
||||
set +x
|
||||
show_systemd_error
|
||||
exit 1
|
||||
fi
|
||||
IPSW_HOST="unix://$XDG_RUNTIME_DIR/ipsw.sock" $BIN/ipswd version
|
||||
systemctl --user enable "${SYSTEMD_UNIT}"
|
||||
)
|
||||
INFO "Installed ${SYSTEMD_UNIT} successfully."
|
||||
INFO "To control ${SYSTEMD_UNIT}, run: \`systemctl --user (start|stop|restart) ${SYSTEMD_UNIT}\`"
|
||||
INFO "To run ${SYSTEMD_UNIT} on system startup, run: \`sudo loginctl enable-linger $(id -un)\`"
|
||||
echo
|
||||
}
|
||||
|
||||
main() {
|
||||
init
|
||||
install_systemd
|
||||
echo " 🎉 Done!"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
@@ -0,0 +1,111 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -o errexit
|
||||
set -o nounset
|
||||
set -o pipefail
|
||||
if [[ "${TRACE-0}" == "1" ]]; then
|
||||
set -o xtrace
|
||||
fi
|
||||
|
||||
# utility functions
|
||||
INFO() {
|
||||
/bin/echo -e "\e[104m\e[97m[INFO]\e[49m\e[39m $@"
|
||||
}
|
||||
|
||||
WARNING() {
|
||||
/bin/echo >&2 -e "\e[101m\e[97m[WARNING]\e[49m\e[39m $@"
|
||||
}
|
||||
|
||||
ERROR() {
|
||||
/bin/echo >&2 -e "\e[101m\e[97m[ERROR]\e[49m\e[39m $@"
|
||||
}
|
||||
|
||||
# constants
|
||||
SYSTEMD_UNIT="ipsw.service"
|
||||
# global vars
|
||||
BIN=""
|
||||
SYSTEMD=""
|
||||
CFG_DIR=""
|
||||
XDG_RUNTIME_DIR_CREATED=""
|
||||
|
||||
# run checks and also initialize global vars
|
||||
init() {
|
||||
# OS verification: Linux only
|
||||
case "$(uname)" in
|
||||
Linux) ;;
|
||||
|
||||
*)
|
||||
ERROR "ipswd cannot be installed on $(uname)"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
# set SYSTEMD
|
||||
if systemctl --user show-environment >/dev/null 2>&1; then
|
||||
SYSTEMD=1
|
||||
fi
|
||||
# HOME verification
|
||||
if [ -z "${HOME:-}" ] || [ ! -d "$HOME" ]; then
|
||||
ERROR "HOME needs to be set"
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -w "$HOME" ]; then
|
||||
ERROR "HOME needs to be writable"
|
||||
exit 1
|
||||
fi
|
||||
# set CFG_DIR
|
||||
CFG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}"
|
||||
# Validate XDG_RUNTIME_DIR and set XDG_RUNTIME_DIR_CREATED
|
||||
if [ -z "${XDG_RUNTIME_DIR:-}" ] || [ ! -w "$XDG_RUNTIME_DIR" ]; then
|
||||
if [ -n "$SYSTEMD" ]; then
|
||||
ERROR "Aborting because systemd was detected but XDG_RUNTIME_DIR (\"$XDG_RUNTIME_DIR\") is not set, does not exist, or is not writable"
|
||||
ERROR "Hint: this could happen if you changed users with 'su' or 'sudo'. To work around this:"
|
||||
ERROR "- try again by first running with root privileges 'loginctl enable-linger <user>' where <user> is the unprivileged user and export XDG_RUNTIME_DIR to the value of RuntimePath as shown by 'loginctl show-user <user>'"
|
||||
ERROR "- or simply log back in as the desired unprivileged user (ssh works for remote machines, machinectl shell works for local machines)"
|
||||
exit 1
|
||||
fi
|
||||
export XDG_RUNTIME_DIR="$HOME/.docker/run"
|
||||
mkdir -p -m 700 "$XDG_RUNTIME_DIR"
|
||||
XDG_RUNTIME_DIR_CREATED=1
|
||||
fi
|
||||
}
|
||||
|
||||
# CLI subcommand: "uninstall"
|
||||
cmd_uninstall() {
|
||||
# requirements are already checked in init()
|
||||
if [ -z "$SYSTEMD" ]; then
|
||||
INFO "systemd not detected, ${DOCKERD_ROOTLESS_SH} needs to be stopped manually:"
|
||||
else
|
||||
unit_file="${CFG_DIR}/systemd/user/${SYSTEMD_UNIT}"
|
||||
(
|
||||
set -x
|
||||
systemctl --user stop "${SYSTEMD_UNIT}"
|
||||
) || :
|
||||
(
|
||||
set -x
|
||||
systemctl --user disable "${SYSTEMD_UNIT}"
|
||||
) || :
|
||||
rm -f "${unit_file}"
|
||||
INFO "Uninstalled ${SYSTEMD_UNIT}"
|
||||
fi
|
||||
|
||||
if cli_ctx_exists "${CLI_CONTEXT}"; then
|
||||
cli_ctx_rm "${CLI_CONTEXT}"
|
||||
INFO "Deleted CLI context \"${CLI_CONTEXT}\""
|
||||
fi
|
||||
unset DOCKER_HOST
|
||||
unset DOCKER_CONTEXT
|
||||
cli_ctx_use "default"
|
||||
INFO 'Configured CLI use the "default" context.'
|
||||
INFO
|
||||
INFO 'Make sure to unset or update the environment PATH, DOCKER_HOST, and DOCKER_CONTEXT environment variables if you have added them to `~/.bashrc`.'
|
||||
INFO "This uninstallation tool does NOT remove Docker binaries and data."
|
||||
INFO "To remove data, run: \`$BIN/rootlesskit rm -rf $HOME/.local/share/docker\`"
|
||||
}
|
||||
|
||||
main() {
|
||||
init
|
||||
cmd_uninstall
|
||||
echo " 🎉 Done!"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
@@ -1,7 +1,10 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
rm -rf completions
|
||||
mkdir completions
|
||||
mkdir -p completions/{ipsw,ipswd}
|
||||
for sh in bash zsh fish powershell; do
|
||||
go run ./cmd/ipsw/main.go completion "$sh" >"completions/_$sh"
|
||||
go run ./cmd/ipsw/main.go completion "$sh" >"completions/ipsw/_$sh"
|
||||
done
|
||||
for sh in bash zsh fish powershell; do
|
||||
go run ./cmd/ipswd/main.go completion "$sh" >"completions/ipswd/_$sh"
|
||||
done
|
||||
|
||||
@@ -16,7 +16,15 @@ This script generates markdown docs from cli.
|
||||
exit
|
||||
fi
|
||||
|
||||
move_swagger() {
|
||||
if [[ -f api/swagger.json ]]; then
|
||||
cp api/swagger.json www/static/api/swagger.json
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
move_swagger
|
||||
|
||||
SED="sed"
|
||||
if which gsed >/dev/null 2>&1; then
|
||||
SED="gsed"
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
package download
|
||||
@@ -0,0 +1,25 @@
|
||||
package ipsw
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/blacktop/ipsw/internal/download"
|
||||
)
|
||||
|
||||
// GetLatestIosVersion returns the latest iOS version
|
||||
func GetLatestIosVersion(proxy string, insecure bool) (string, error) {
|
||||
assets, err := download.GetAssetSets(proxy, insecure)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get asset latest version: %v", err)
|
||||
}
|
||||
return assets.LatestVersion("iOS", "ios"), nil
|
||||
}
|
||||
|
||||
// GetLatestIosBuild returns the latest iOS build
|
||||
func GetLatestIosBuild() (string, error) { // TODO: add proxy and insecure support
|
||||
itunes, err := download.NewMacOsXML()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to parse itunes XML: %v", err)
|
||||
}
|
||||
return itunes.GetLatestBuild()
|
||||
}
|
||||
@@ -0,0 +1,358 @@
|
||||
// Package dsc implements the `dsc` commands
|
||||
package dsc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/blacktop/go-macho/pkg/codesign"
|
||||
"github.com/blacktop/ipsw/internal/utils"
|
||||
"github.com/blacktop/ipsw/pkg/dyld"
|
||||
)
|
||||
|
||||
// ImportedBy is a struct that contains information about which dyld_shared_cache dylibs import a given dylib
|
||||
type ImportedBy struct {
|
||||
DSC []string `json:"dsc,omitempty"`
|
||||
Apps []string `json:"apps,omitempty"`
|
||||
}
|
||||
|
||||
// Dylib is a struct that contains information about a dyld_shared_cache dylib
|
||||
type Dylib struct {
|
||||
Index int `json:"index,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
UUID string `json:"uuid,omitempty"`
|
||||
LoadAddress uint64 `json:"load_address,omitempty"`
|
||||
}
|
||||
|
||||
// Info is a struct that contains information about a dyld_shared_cache file
|
||||
type Info struct {
|
||||
Magic string `json:"magic,omitempty"`
|
||||
UUID string `json:"uuid,omitempty"`
|
||||
Platform string `json:"platform,omitempty"`
|
||||
MaxSlide int `json:"max_slide,omitempty"`
|
||||
SubCacheArrayCount int `json:"num_sub_caches,omitempty"`
|
||||
SubCacheGroupID int `json:"sub_cache_group_id,omitempty"`
|
||||
SymSubCacheUUID string `json:"sym_sub_cache_uuid,omitempty"`
|
||||
Mappings map[string][]dyld.CacheMappingWithSlideInfo `json:"mappings,omitempty"`
|
||||
CodeSignature map[string]codesign.CodeSignature `json:"code_signature,omitempty"`
|
||||
Dylibs []Dylib `json:"dylibs,omitempty"`
|
||||
}
|
||||
|
||||
// Symbol is a struct that contains information about a dyld_shared_cache symbol
|
||||
type Symbol struct {
|
||||
Address uint64 `json:"address,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Image string `json:"image,omitempty"`
|
||||
Pattern string `json:"pattern,omitempty"`
|
||||
}
|
||||
|
||||
// String is a struct that contains information about a dyld_shared_cache string
|
||||
type String struct {
|
||||
Address uint64 `json:"address,omitempty"`
|
||||
Image string `json:"image,omitempty"`
|
||||
String string `json:"string,omitempty"`
|
||||
}
|
||||
|
||||
// GetDylibsThatImport returns a list of dylibs that import the given dylib
|
||||
func GetDylibsThatImport(f *dyld.File, name string) (*ImportedBy, error) {
|
||||
var importedBy ImportedBy
|
||||
|
||||
image, err := f.Image(name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("dylib not in DSC: %v", err)
|
||||
}
|
||||
|
||||
if f.SupportsDylibPrebuiltLoader() {
|
||||
for _, img := range f.Images {
|
||||
pbl, err := f.GetDylibPrebuiltLoader(img.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, dep := range pbl.Dependents {
|
||||
if strings.EqualFold(dep.Name, image.Name) {
|
||||
importedBy.DSC = append(importedBy.DSC, img.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, img := range f.Images {
|
||||
m, err := img.GetPartialMacho()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, imp := range m.ImportedLibraries() {
|
||||
if strings.EqualFold(imp, image.Name) {
|
||||
importedBy.DSC = append(importedBy.DSC, img.Name)
|
||||
}
|
||||
}
|
||||
m.Close()
|
||||
}
|
||||
}
|
||||
|
||||
if f.SupportsPrebuiltLoaderSet() {
|
||||
if err := f.ForEachLaunchLoaderSet(func(execPath string, pset *dyld.PrebuiltLoaderSet) {
|
||||
for _, loader := range pset.Loaders {
|
||||
for _, dep := range loader.Dependents {
|
||||
if strings.EqualFold(dep.Name, image.Name) {
|
||||
if execPath != loader.Path {
|
||||
importedBy.Apps = append(importedBy.Apps, fmt.Sprintf("%s (%s)", execPath, loader.Path))
|
||||
} else {
|
||||
importedBy.Apps = append(importedBy.Apps, execPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &importedBy, nil
|
||||
}
|
||||
|
||||
// GetInfo returns a Info struct for a given dyld_shared_cache file
|
||||
func GetInfo(f *dyld.File) (*Info, error) {
|
||||
info := &Info{
|
||||
Magic: f.Headers[f.UUID].Magic.String(),
|
||||
UUID: f.UUID.String(),
|
||||
Platform: f.Headers[f.UUID].Platform.String(),
|
||||
MaxSlide: int(f.Headers[f.UUID].MaxSlide),
|
||||
}
|
||||
|
||||
info.Mappings = make(map[string][]dyld.CacheMappingWithSlideInfo)
|
||||
|
||||
for u, mp := range f.MappingsWithSlideInfo {
|
||||
for _, m := range mp {
|
||||
info.Mappings[u.String()] = append(info.Mappings[u.String()], *m)
|
||||
}
|
||||
}
|
||||
|
||||
info.CodeSignature = make(map[string]codesign.CodeSignature)
|
||||
|
||||
for u, cs := range f.CodeSignatures {
|
||||
info.CodeSignature[u.String()] = *cs
|
||||
}
|
||||
|
||||
for idx, img := range f.Images {
|
||||
m, err := img.GetPartialMacho()
|
||||
if err != nil {
|
||||
continue
|
||||
// return fmt.Errorf("failed to create partial MachO for image %s: %v", img.Name, err)
|
||||
}
|
||||
info.Dylibs = append(info.Dylibs, Dylib{
|
||||
Index: idx + 1,
|
||||
Name: img.Name,
|
||||
Version: m.SourceVersion().Version.String(),
|
||||
UUID: m.UUID().String(),
|
||||
LoadAddress: img.Info.Address,
|
||||
})
|
||||
m.Close()
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// GetSymbols returns a list of symbols from a dyld_shared_cache file for a given list of lookup symbol structs
|
||||
func GetSymbols(f *dyld.File, lookups []Symbol) ([]Symbol, error) {
|
||||
var syms []Symbol
|
||||
|
||||
// group syms by image
|
||||
sym2imgs := make(map[string][]Symbol)
|
||||
for _, lookup := range lookups {
|
||||
if len(lookup.Pattern) == 0 {
|
||||
return nil, fmt.Errorf("pattern cannot be empty: %v", lookup)
|
||||
}
|
||||
if len(lookup.Image) > 0 {
|
||||
image, err := f.Image(lookup.Image)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get image %s: %v", lookup.Image, err)
|
||||
}
|
||||
sym2imgs[image.Name] = append(sym2imgs[image.Name], lookup)
|
||||
} else {
|
||||
sym2imgs["unknown"] = append(sym2imgs["unknown"], lookup)
|
||||
}
|
||||
}
|
||||
|
||||
for imageName, lookups := range sym2imgs {
|
||||
if imageName == "unknown" {
|
||||
for _, lookup := range lookups {
|
||||
re, err := regexp.Compile(lookup.Pattern)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid regex for %v: %w", lookup, err)
|
||||
}
|
||||
for _, image := range f.Images {
|
||||
m, err := image.GetPartialMacho()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := image.ParseLocalSymbols(false); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, lsym := range image.LocalSymbols {
|
||||
if re.MatchString(lsym.Name) {
|
||||
var sec string
|
||||
if lsym.Sect > 0 && int(lsym.Sect) <= len(m.Sections) {
|
||||
sec = fmt.Sprintf("%s.%s", m.Sections[lsym.Sect-1].Seg, m.Sections[lsym.Sect-1].Name)
|
||||
}
|
||||
syms = append(syms, Symbol{
|
||||
Name: lsym.Name,
|
||||
Address: lsym.Value,
|
||||
Type: lsym.Type.String(sec),
|
||||
Image: filepath.Base(image.Name),
|
||||
})
|
||||
}
|
||||
}
|
||||
if err := image.ParsePublicSymbols(false); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, sym := range image.PublicSymbols {
|
||||
if re.MatchString(sym.Name) {
|
||||
syms = append(syms, Symbol{
|
||||
Name: sym.Name,
|
||||
Address: sym.Address,
|
||||
Type: sym.Type,
|
||||
Image: filepath.Base(image.Name),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else { // image is known
|
||||
image, err := f.Image(imageName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, lookup := range lookups {
|
||||
re, err := regexp.Compile(lookup.Pattern)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid regex for %v: %w", lookup, err)
|
||||
}
|
||||
m, err := image.GetPartialMacho()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := image.ParseLocalSymbols(false); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, lsym := range image.LocalSymbols {
|
||||
if re.MatchString(lsym.Name) {
|
||||
var sec string
|
||||
if lsym.Sect > 0 && int(lsym.Sect) <= len(m.Sections) {
|
||||
sec = fmt.Sprintf("%s.%s", m.Sections[lsym.Sect-1].Seg, m.Sections[lsym.Sect-1].Name)
|
||||
}
|
||||
syms = append(syms, Symbol{
|
||||
Name: lsym.Name,
|
||||
Address: lsym.Value,
|
||||
Type: lsym.Type.String(sec),
|
||||
Image: filepath.Base(image.Name),
|
||||
})
|
||||
}
|
||||
}
|
||||
if err := image.ParsePublicSymbols(false); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, sym := range image.PublicSymbols {
|
||||
if re.MatchString(sym.Name) {
|
||||
syms = append(syms, Symbol{
|
||||
Name: sym.Name,
|
||||
Address: sym.Address,
|
||||
Type: sym.Type,
|
||||
Image: filepath.Base(image.Name),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return syms, nil
|
||||
}
|
||||
|
||||
// GetStrings returns a list of strings from a dyld_shared_cache file for a given regex pattern
|
||||
func GetStrings(f *dyld.File, pattern string) ([]String, error) {
|
||||
var strs []String
|
||||
|
||||
if len(pattern) == 0 {
|
||||
return nil, fmt.Errorf("pattern cannot be empty")
|
||||
}
|
||||
|
||||
strRE, err := regexp.Compile(pattern)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid regex: %w", err)
|
||||
}
|
||||
|
||||
for _, i := range f.Images {
|
||||
m, err := i.GetMacho()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create MachO for image %s: %v", i.Name, err)
|
||||
}
|
||||
|
||||
// cstrings
|
||||
for _, sec := range m.Sections {
|
||||
if sec.Flags.IsCstringLiterals() || sec.Seg == "__TEXT" && sec.Name == "__const" {
|
||||
uuid, off, err := f.GetOffset(sec.Addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get offset for %s.%s: %v", sec.Seg, sec.Name, err)
|
||||
}
|
||||
dat, err := f.ReadBytesForUUID(uuid, int64(off), sec.Size)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read cstrings in %s.%s: %v", sec.Seg, sec.Name, err)
|
||||
}
|
||||
|
||||
csr := bytes.NewBuffer(dat)
|
||||
|
||||
for {
|
||||
pos := sec.Addr + uint64(csr.Cap()-csr.Len())
|
||||
|
||||
s, err := csr.ReadString('\x00')
|
||||
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read string: %v", err)
|
||||
}
|
||||
|
||||
s = strings.Trim(s, "\x00")
|
||||
|
||||
if len(s) > 0 {
|
||||
if (sec.Seg == "__TEXT" && sec.Name == "__const") && !utils.IsASCII(s) {
|
||||
continue // skip non-ascii strings when dumping __TEXT.__const
|
||||
}
|
||||
if strRE.MatchString(s) {
|
||||
strs = append(strs, String{
|
||||
Address: pos,
|
||||
Image: filepath.Base(i.Name),
|
||||
String: s,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// objc cfstrings
|
||||
if cfstrs, err := m.GetCFStrings(); err == nil {
|
||||
if len(cfstrs) > 0 {
|
||||
for _, cfstr := range cfstrs {
|
||||
if strRE.MatchString(cfstr.Name) {
|
||||
strs = append(strs, String{
|
||||
Address: cfstr.Address,
|
||||
Image: filepath.Base(i.Name),
|
||||
String: cfstr.Name,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return strs, nil
|
||||
}
|
||||
@@ -74,27 +74,29 @@ func GetDatabase(ipswPath, entDBPath string) (map[string]string, error) {
|
||||
}
|
||||
}
|
||||
|
||||
buff := new(bytes.Buffer)
|
||||
if len(entDBPath) > 0 {
|
||||
buff := new(bytes.Buffer)
|
||||
|
||||
e := gob.NewEncoder(buff)
|
||||
e := gob.NewEncoder(buff)
|
||||
|
||||
// Encoding the map
|
||||
err := e.Encode(entDB)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to encode entitlement db to binary: %v", err)
|
||||
}
|
||||
// Encoding the map
|
||||
err := e.Encode(entDB)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to encode entitlement db to binary: %v", err)
|
||||
}
|
||||
|
||||
of, err := os.Create(entDBPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create file %s: %v", ipswPath+".entDB", err)
|
||||
}
|
||||
defer of.Close()
|
||||
of, err := os.Create(entDBPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create file %s: %v", ipswPath+".entDB", err)
|
||||
}
|
||||
defer of.Close()
|
||||
|
||||
gzw := gzip.NewWriter(of)
|
||||
defer gzw.Close()
|
||||
gzw := gzip.NewWriter(of)
|
||||
defer gzw.Close()
|
||||
|
||||
if _, err := buff.WriteTo(gzw); err != nil {
|
||||
return nil, fmt.Errorf("failed to write entitlement db to gzip file: %v", err)
|
||||
if _, err := buff.WriteTo(gzw); err != nil {
|
||||
return nil, fmt.Errorf("failed to write entitlement db to gzip file: %v", err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.Info("Found ipsw entitlement database file...")
|
||||
|
||||
@@ -0,0 +1,349 @@
|
||||
// Package extract contains the extract commands.
|
||||
package extract
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
|
||||
"github.com/apex/log"
|
||||
"github.com/blacktop/ipsw/internal/download"
|
||||
"github.com/blacktop/ipsw/internal/utils"
|
||||
"github.com/blacktop/ipsw/pkg/dyld"
|
||||
"github.com/blacktop/ipsw/pkg/img4"
|
||||
"github.com/blacktop/ipsw/pkg/info"
|
||||
"github.com/blacktop/ipsw/pkg/kernelcache"
|
||||
)
|
||||
|
||||
// Config is the extract command configuration.
|
||||
//
|
||||
// swagger:model ExtractConfig
|
||||
type Config struct {
|
||||
// path to the IPSW
|
||||
//
|
||||
// required: true
|
||||
IPSW string `json:"ipsw,omitempty"`
|
||||
// url to the remote IPSW
|
||||
//
|
||||
// required: true
|
||||
URL string `json:"url,omitempty"`
|
||||
// regex pattern to search for in the IPSW
|
||||
//
|
||||
// required: true
|
||||
Pattern string `json:"pattern,omitempty"`
|
||||
// arches of the DSCs to extract
|
||||
//
|
||||
// required: true
|
||||
Arches []string `json:"arches,omitempty"`
|
||||
// http proxy to use
|
||||
//
|
||||
// required: true
|
||||
Proxy string `json:"proxy,omitempty"`
|
||||
// don't verify the certificate chain
|
||||
//
|
||||
// required: true
|
||||
Insecure bool `json:"insecure,omitempty"`
|
||||
// search the DMGs for files
|
||||
//
|
||||
// required: true
|
||||
DMGs bool `json:"dmgs,omitempty"`
|
||||
// type of DMG to extract
|
||||
//
|
||||
// required: true
|
||||
// pattern: (app|sys|fs)
|
||||
DmgType string `json:"dmg_type,omitempty"`
|
||||
// flatten the extracted files paths (remove the folders)
|
||||
//
|
||||
// required: true
|
||||
Flatten bool `json:"flatten,omitempty"`
|
||||
// show the progress bar (when using the CLI)
|
||||
//
|
||||
// required: false
|
||||
Progress bool `json:"progress,omitempty"`
|
||||
// output directory to write extracted files to
|
||||
//
|
||||
// required: true
|
||||
Output string `json:"output,omitempty"`
|
||||
}
|
||||
|
||||
func isURL(str string) bool {
|
||||
u, err := url.Parse(str)
|
||||
return err == nil && u.Scheme != "" && u.Host != ""
|
||||
}
|
||||
|
||||
func getFolder(c *Config) (*info.Info, string, error) {
|
||||
c.IPSW = filepath.Clean(c.IPSW)
|
||||
i, err := info.Parse(c.IPSW)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("failed to parse plists in IPSW: %v", err)
|
||||
}
|
||||
folder, err := i.GetFolder()
|
||||
if err != nil {
|
||||
log.Errorf("failed to get folder from IPSW metadata: %v", err)
|
||||
}
|
||||
return i, folder, nil
|
||||
}
|
||||
|
||||
func getRemoteFolder(c *Config) (*info.Info, *zip.Reader, string, error) {
|
||||
zr, err := download.NewRemoteZipReader(c.URL, &download.RemoteConfig{
|
||||
Proxy: c.Proxy,
|
||||
Insecure: c.Insecure,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, "", fmt.Errorf("unable to download remote zip: %v", err)
|
||||
}
|
||||
i, err := info.ParseZipFiles(zr.File)
|
||||
if err != nil {
|
||||
return nil, nil, "", fmt.Errorf("failed to parse plists in remote zip: %v", err)
|
||||
}
|
||||
folder, err := i.GetFolder()
|
||||
if err != nil {
|
||||
return nil, nil, "", fmt.Errorf("failed to get folder from remote zip metadata: %v", err)
|
||||
}
|
||||
return i, zr, folder, nil
|
||||
}
|
||||
|
||||
// Kernelcache extracts the kernelcache from an IPSW
|
||||
func Kernelcache(c *Config) ([]string, error) {
|
||||
if len(c.IPSW) > 0 {
|
||||
_, folder, err := getFolder(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return kernelcache.Extract(c.IPSW, filepath.Join(filepath.Clean(c.Output), folder))
|
||||
} else if len(c.URL) > 0 {
|
||||
if !isURL(c.URL) {
|
||||
return nil, fmt.Errorf("invalid URL provided: %s", c.URL)
|
||||
}
|
||||
_, zr, folder, err := getRemoteFolder(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return kernelcache.RemoteParse(zr, filepath.Join(filepath.Clean(c.Output), folder))
|
||||
}
|
||||
return nil, fmt.Errorf("no IPSW or URL provided")
|
||||
}
|
||||
|
||||
// DSC extracts the DSC file from an IPSW
|
||||
func DSC(c *Config) ([]string, error) {
|
||||
if len(c.IPSW) > 0 {
|
||||
_, folder, err := getFolder(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dyld.Extract(c.IPSW, filepath.Join(filepath.Clean(c.Output), folder), c.Arches)
|
||||
} else if len(c.URL) > 0 {
|
||||
if !isURL(c.URL) {
|
||||
return nil, fmt.Errorf("invalid URL provided: %s", c.URL)
|
||||
}
|
||||
i, zr, folder, err := getRemoteFolder(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sysDMG, err := i.GetSystemOsDmg()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("only iOS16.x/macOS13.x supported: failed to get SystemOS DMG from remote zip metadata: %v", err)
|
||||
}
|
||||
if len(sysDMG) == 0 {
|
||||
return nil, fmt.Errorf("only iOS16.x/macOS13.x supported: no SystemOS DMG found in remote zip metadata")
|
||||
}
|
||||
tmpDIR, err := os.MkdirTemp("", "ipsw_extract_remote_dyld")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create temporary directory to store SystemOS DMG: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDIR)
|
||||
if _, err := utils.SearchZip(zr.File, regexp.MustCompile(fmt.Sprintf("^%s$", sysDMG)), tmpDIR, c.Flatten, true); err != nil {
|
||||
return nil, fmt.Errorf("failed to extract SystemOS DMG from remote IPSW: %v", err)
|
||||
}
|
||||
return dyld.ExtractFromDMG(i, filepath.Join(tmpDIR, sysDMG), filepath.Join(filepath.Clean(c.Output), folder), c.Arches)
|
||||
}
|
||||
return nil, fmt.Errorf("no IPSW or URL provided")
|
||||
}
|
||||
|
||||
// DMG extracts the DMG from an IPSW
|
||||
func DMG(c *Config) ([]string, error) {
|
||||
if len(c.IPSW) == 0 && len(c.URL) == 0 {
|
||||
return nil, fmt.Errorf("no IPSW or URL provided")
|
||||
}
|
||||
|
||||
var err error
|
||||
var i *info.Info
|
||||
var folder string
|
||||
var zr *zip.Reader
|
||||
|
||||
if len(c.IPSW) > 0 {
|
||||
i, folder, err = getFolder(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f, err := os.Open(filepath.Clean(c.IPSW))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open IPSW: %v", err)
|
||||
}
|
||||
defer f.Close()
|
||||
finfo, err := f.Stat()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to stat IPSW: %v", err)
|
||||
}
|
||||
zr, err = zip.NewReader(f, finfo.Size())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open IPSW: %v", err)
|
||||
}
|
||||
} else if len(c.URL) > 0 {
|
||||
if !isURL(c.URL) {
|
||||
return nil, fmt.Errorf("invalid URL provided: %s", c.URL)
|
||||
}
|
||||
i, zr, folder, err = getRemoteFolder(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var dmgPath string
|
||||
switch c.DmgType {
|
||||
case "app":
|
||||
dmgPath, err = i.GetAppOsDmg()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to find app DMG in IPSW: %v", err)
|
||||
}
|
||||
case "sys":
|
||||
dmgPath, err = i.GetSystemOsDmg()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to find system DMG in IPSW: %v", err)
|
||||
}
|
||||
case "fs":
|
||||
dmgPath, err = i.GetFileSystemOsDmg()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to find filesystem DMG in IPSW: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return utils.SearchZip(zr.File, regexp.MustCompile(dmgPath), filepath.Join(filepath.Clean(c.Output), folder), c.Flatten, c.Progress)
|
||||
}
|
||||
|
||||
// Keybags extracts the keybags from an IPSW
|
||||
func Keybags(c *Config) (string, error) {
|
||||
if len(c.IPSW) == 0 && len(c.URL) == 0 {
|
||||
return "", fmt.Errorf("no IPSW or URL provided")
|
||||
}
|
||||
|
||||
var err error
|
||||
var i *info.Info
|
||||
var folder string
|
||||
var zr *zip.Reader
|
||||
|
||||
if len(c.IPSW) > 0 {
|
||||
i, folder, err = getFolder(c)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
zr, err := zip.OpenReader(filepath.Clean(c.IPSW))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to open IPSW: %v", err)
|
||||
}
|
||||
defer zr.Close()
|
||||
} else if len(c.URL) > 0 {
|
||||
if !isURL(c.URL) {
|
||||
return "", fmt.Errorf("invalid URL provided: %s", c.URL)
|
||||
}
|
||||
i, zr, folder, err = getRemoteFolder(c)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
kbags, err := img4.ParseZipKeyBags(zr.File, i, c.Pattern)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to parse im4p kbags: %v", err)
|
||||
}
|
||||
|
||||
out, err := json.Marshal(kbags)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to marshal im4p kbags: %v", err)
|
||||
}
|
||||
|
||||
fname := filepath.Join(filepath.Join(filepath.Clean(c.Output), folder), "kbags.json")
|
||||
if err := os.MkdirAll(filepath.Dir(fname), 0750); err != nil {
|
||||
return "", fmt.Errorf("failed to create directory %s: %v", filepath.Dir(fname), err)
|
||||
}
|
||||
if err := os.WriteFile(fname, out, 0660); err != nil {
|
||||
return "", fmt.Errorf("failed to write %s: %v", filepath.Join(filepath.Join(filepath.Clean(c.Output), folder), "kbags.json"), err)
|
||||
}
|
||||
|
||||
return fname, nil
|
||||
}
|
||||
|
||||
// Search searches for files matching a pattern in an IPSW
|
||||
func Search(c *Config) ([]string, error) {
|
||||
var artifacts []string
|
||||
|
||||
if len(c.Pattern) == 0 {
|
||||
return nil, fmt.Errorf("no pattern provided")
|
||||
}
|
||||
re, err := regexp.Compile(c.Pattern)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to compile regexp '%s': %v", c.Pattern, err)
|
||||
}
|
||||
if len(c.IPSW) > 0 {
|
||||
i, folder, err := getFolder(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
destPath := filepath.Join(filepath.Clean(c.Output), folder)
|
||||
zr, err := zip.OpenReader(c.IPSW)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open IPSW: %v", err)
|
||||
}
|
||||
defer zr.Close()
|
||||
out, err := utils.SearchZip(zr.File, re, destPath, c.Flatten, false)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to extract files matching pattern: %v", err)
|
||||
}
|
||||
artifacts = append(artifacts, out...)
|
||||
if c.DMGs { // SEARCH THE DMGs
|
||||
if appOS, err := i.GetAppOsDmg(); err == nil {
|
||||
out, err := utils.ExtractFromDMG(c.IPSW, appOS, destPath, re)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to extract files from AppOS %s: %v", appOS, err)
|
||||
}
|
||||
artifacts = append(artifacts, out...)
|
||||
}
|
||||
if systemOS, err := i.GetSystemOsDmg(); err == nil {
|
||||
out, err := utils.ExtractFromDMG(c.IPSW, systemOS, destPath, re)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to extract files from SystemOS %s: %v", systemOS, err)
|
||||
}
|
||||
artifacts = append(artifacts, out...)
|
||||
}
|
||||
if fsOS, err := i.GetFileSystemOsDmg(); err == nil {
|
||||
out, err := utils.ExtractFromDMG(c.IPSW, fsOS, destPath, re)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to extract files from filesystem %s: %v", fsOS, err)
|
||||
}
|
||||
artifacts = append(artifacts, out...)
|
||||
}
|
||||
}
|
||||
return artifacts, nil
|
||||
} else if len(c.URL) > 0 {
|
||||
if !isURL(c.URL) {
|
||||
return nil, fmt.Errorf("invalid URL provided: %s", c.URL)
|
||||
}
|
||||
if c.DMGs { // SEARCH THE DMGs
|
||||
return nil, fmt.Errorf("searching DMGs in remote IPSW is not supported")
|
||||
}
|
||||
_, zr, folder, err := getRemoteFolder(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
artifacts, err = utils.SearchZip(zr.File, re, filepath.Join(filepath.Clean(c.Output), folder), c.Flatten, true)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to extract files matching pattern '%s' in remote IPSW: %v", c.Pattern, err)
|
||||
}
|
||||
return artifacts, nil
|
||||
}
|
||||
return nil, fmt.Errorf("no IPSW or URL provided")
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
// Package mount provides the mount command
|
||||
package mount
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/blacktop/ipsw/internal/utils"
|
||||
"github.com/blacktop/ipsw/pkg/info"
|
||||
)
|
||||
|
||||
var dmgTypes = []string{"fs", "sys", "app"}
|
||||
|
||||
// Context is the mount context
|
||||
type Context struct {
|
||||
MountPoint string `json:"mount_point" binding:"required"`
|
||||
DmgPath string `json:"dmg_path,omitempty"` // FIXME: required on linux
|
||||
AlreadyMounted bool `json:"already_mounted,omitempty"`
|
||||
}
|
||||
|
||||
// Unmount will unmount a DMG and remove the DMG source file
|
||||
func (c Context) Unmount() error {
|
||||
if info, err := utils.MountInfo(); err == nil { // darwin only
|
||||
if image := info.Mount(c.MountPoint); image != nil {
|
||||
c.DmgPath = filepath.Clean(image.ImagePath)
|
||||
}
|
||||
}
|
||||
if err := utils.Retry(3, 2*time.Second, func() error {
|
||||
return utils.Unmount(c.MountPoint, false)
|
||||
}); err != nil {
|
||||
return fmt.Errorf("failed to unmount %s at %s: %v", c.DmgPath, c.MountPoint, err)
|
||||
}
|
||||
// TODO: check if DmgPath is safe before removing (should be in /tmp and should be src of mount)
|
||||
return os.Remove(filepath.Clean(c.DmgPath))
|
||||
}
|
||||
|
||||
// DmgInIPSW will mount a DMG from an IPSW
|
||||
func DmgInIPSW(path, typ string) (*Context, error) {
|
||||
ipswPath := filepath.Clean(path)
|
||||
|
||||
i, err := info.Parse(ipswPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse IPSW: %v", err)
|
||||
}
|
||||
|
||||
var dmgPath string
|
||||
|
||||
switch typ {
|
||||
case "fs":
|
||||
dmgPath, err = i.GetFileSystemOsDmg()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get filesystem DMG: %v", err)
|
||||
}
|
||||
case "sys":
|
||||
dmgPath, err = i.GetSystemOsDmg()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get SystemOS DMG: %v", err)
|
||||
}
|
||||
case "app":
|
||||
dmgPath, err = i.GetAppOsDmg()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get AppOS DMG: %v", err)
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid subcommand: %s; must be one of: '%s'", typ, strings.Join(dmgTypes, "', '"))
|
||||
}
|
||||
|
||||
extractedDMG := filepath.Join(os.TempDir(), dmgPath)
|
||||
|
||||
if _, err := os.Stat(extractedDMG); os.IsNotExist(err) {
|
||||
dmgs, err := utils.Unzip(ipswPath, os.TempDir(), func(f *zip.File) bool {
|
||||
return strings.EqualFold(filepath.Base(f.Name), dmgPath)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to extract %s from IPSW: %v", dmgPath, err)
|
||||
}
|
||||
if len(dmgs) == 0 {
|
||||
return nil, fmt.Errorf("failed to find %s in IPSW", dmgPath)
|
||||
}
|
||||
}
|
||||
|
||||
mp, am, err := utils.MountFS(extractedDMG)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to mount %s: %v", extractedDMG, err)
|
||||
}
|
||||
|
||||
return &Context{
|
||||
DmgPath: extractedDMG,
|
||||
MountPoint: mp,
|
||||
AlreadyMounted: am,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
// Package config is used to load the configuration file
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type daemon struct {
|
||||
Host string `json:"host"`
|
||||
Port int `json:"port"`
|
||||
Socket string `json:"socket"`
|
||||
Debug bool `json:"debug"`
|
||||
}
|
||||
|
||||
type database struct {
|
||||
Name string `json:"database"`
|
||||
Host string `json:"host"`
|
||||
Port string `json:"port"`
|
||||
User string `json:"user"`
|
||||
Password string `json:"password"`
|
||||
SSLMode string `json:"sslmode"`
|
||||
}
|
||||
|
||||
// Config is the configuration struct
|
||||
type Config struct {
|
||||
Daemon daemon `json:"daemon"`
|
||||
Database database `json:"database"`
|
||||
}
|
||||
|
||||
func (c *Config) verify() error {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return fmt.Errorf("config: failed to get user home directory: %v", err)
|
||||
}
|
||||
if c.Daemon.Host == "" && c.Daemon.Port == 0 && c.Daemon.Socket == "" {
|
||||
// c.Daemon.Socket = "/var/run/ipsw.sock"
|
||||
c.Daemon.Socket = filepath.Join(home, ".config", "ipsw", "ipsw.sock")
|
||||
} else if c.Daemon.Host != "" && c.Daemon.Socket != "" {
|
||||
return fmt.Errorf("config: host and socket cannot be set at the same time")
|
||||
} else if c.Daemon.Host != "" && c.Daemon.Port == 0 {
|
||||
return fmt.Errorf("config: port must be set if host is set")
|
||||
} else if c.Daemon.Host == "" && c.Daemon.Port != 0 {
|
||||
c.Daemon.Host = "localhost"
|
||||
} else if strings.HasPrefix(c.Daemon.Socket, "~/") {
|
||||
c.Daemon.Socket = filepath.Join(home, c.Daemon.Socket[2:]) // TODO: is this bad practice?
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadConfig loads the configuration file
|
||||
func LoadConfig() (*Config, error) {
|
||||
var c *Config
|
||||
|
||||
if err := viper.Unmarshal(&c); err != nil {
|
||||
return nil, fmt.Errorf("config: failed to unmarshal: %v", err)
|
||||
}
|
||||
|
||||
if err := c.verify(); err != nil {
|
||||
return nil, fmt.Errorf("config: failed to verify: %v", err)
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// Package daemon provides the daemon interface and implementation.
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"github.com/blacktop/ipsw/api/server"
|
||||
"github.com/blacktop/ipsw/internal/config"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// Daemon is the interface that describes an ipsw daemon.
|
||||
type Daemon interface {
|
||||
// Start starts the daemon.
|
||||
Start() error
|
||||
// Stop stops the daemon.
|
||||
Stop() error
|
||||
}
|
||||
|
||||
type daemon struct {
|
||||
server *server.Server
|
||||
conf *config.Config
|
||||
}
|
||||
|
||||
// NewDaemon creates a new daemon.
|
||||
func NewDaemon() Daemon {
|
||||
return &daemon{}
|
||||
}
|
||||
|
||||
func (d *daemon) Start() (err error) {
|
||||
d.conf, err = config.LoadConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if d.conf.Daemon.Debug {
|
||||
gin.SetMode(gin.DebugMode)
|
||||
} else {
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
}
|
||||
d.server = server.NewServer(&server.Config{
|
||||
Host: d.conf.Daemon.Host,
|
||||
Port: d.conf.Daemon.Port,
|
||||
Socket: d.conf.Daemon.Socket,
|
||||
Debug: d.conf.Daemon.Debug,
|
||||
})
|
||||
return d.server.Start()
|
||||
}
|
||||
|
||||
func (d *daemon) Stop() error {
|
||||
return d.server.Stop()
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// Package db provides a database interface and implementations.
|
||||
package db
|
||||
|
||||
import "github.com/blacktop/ipsw/internal/models"
|
||||
|
||||
// Database is the interface that wraps the basic database operations.
|
||||
type Database interface {
|
||||
// Connect connects to the database.
|
||||
Connect() error
|
||||
|
||||
// Create creates a new entry in the database.
|
||||
// It returns ErrAlreadyExists if the key already exists.
|
||||
Create(i *models.IPSW) error
|
||||
|
||||
// Get returns the value for the given key.
|
||||
// It returns ErrNotFound if the key does not exist.
|
||||
Get(key uint) (*models.IPSW, error)
|
||||
|
||||
// Set sets the value for the given key.
|
||||
// It overwrites any previous value for that key.
|
||||
Set(key uint, value *models.IPSW) error
|
||||
|
||||
// Delete removes the given key.
|
||||
// It returns ErrNotFound if the key does not exist.
|
||||
Delete(key uint) error
|
||||
|
||||
// Close closes the database.
|
||||
// It returns ErrClosed if the database is already closed.
|
||||
Close() error
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"os"
|
||||
|
||||
"github.com/blacktop/ipsw/internal/models"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Memory is a database that stores data in memory.
|
||||
type Memory struct {
|
||||
IPSWs map[uint]*models.IPSW
|
||||
Path string
|
||||
}
|
||||
|
||||
// NewInMemory creates a new in-memory database.
|
||||
func NewInMemory(path string) Database {
|
||||
return Memory{
|
||||
IPSWs: make(map[uint]*models.IPSW),
|
||||
Path: path,
|
||||
}
|
||||
}
|
||||
|
||||
// Connect connects to the database.
|
||||
func (m Memory) Connect() error {
|
||||
f, err := os.Open(m.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
return gob.NewDecoder(f).Decode(&m.IPSWs)
|
||||
}
|
||||
|
||||
// Create creates a new entry in the database.
|
||||
// It returns ErrAlreadyExists if the key already exists.
|
||||
func (m Memory) Create(i *models.IPSW) error {
|
||||
m.IPSWs[i.ID] = i
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get returns the value for the given key.
|
||||
// It returns ErrNotFound if the key does not exist.
|
||||
func (m Memory) Get(id uint) (*models.IPSW, error) {
|
||||
pet, exists := m.IPSWs[id]
|
||||
if !exists {
|
||||
return nil, errors.Errorf("no pet found with id: %s", id)
|
||||
}
|
||||
return pet, nil
|
||||
}
|
||||
|
||||
// Set sets the value for the given key.
|
||||
// It overwrites any previous value for that key.
|
||||
func (m Memory) Set(key uint, value *models.IPSW) error {
|
||||
m.IPSWs[key] = value
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m Memory) List(version string) ([]*models.IPSW, error) {
|
||||
ipsws := []*models.IPSW{}
|
||||
for _, p := range m.IPSWs {
|
||||
if p.Version == version {
|
||||
ipsws = append(ipsws, p)
|
||||
}
|
||||
}
|
||||
return ipsws, nil
|
||||
}
|
||||
|
||||
// Delete removes the given key.
|
||||
// It returns ErrNotFound if the key does not exist.
|
||||
func (m Memory) Delete(id uint) error {
|
||||
delete(m.IPSWs, id)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close closes the database.
|
||||
// It returns ErrClosed if the database is already closed.
|
||||
func (m Memory) Close() error {
|
||||
f, err := os.Open(m.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
return gob.NewEncoder(f).Encode(m.IPSWs)
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/blacktop/ipsw/internal/models"
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Postgres is a database that stores data in a Postgres database.
|
||||
type Postgres struct {
|
||||
URL string
|
||||
Host string
|
||||
Port string
|
||||
User string
|
||||
Password string
|
||||
Database string
|
||||
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewPostgres creates a new Postgres database.
|
||||
func NewPostgres(host, port, user, password, database string) Database {
|
||||
return Postgres{
|
||||
Host: host,
|
||||
Port: port,
|
||||
User: user,
|
||||
Password: password,
|
||||
Database: database,
|
||||
}
|
||||
}
|
||||
|
||||
// Connect connects to the database.
|
||||
func (p Postgres) Connect() (err error) {
|
||||
p.db, err = gorm.Open(postgres.Open(fmt.Sprintf(
|
||||
"host=%s port=%s user=%s dbname=%s password=%s sslmode=disable",
|
||||
p.Host, p.Port, p.User, p.Database, p.Password,
|
||||
)), &gorm.Config{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to connect postgres database: %w", err)
|
||||
}
|
||||
return p.db.AutoMigrate(&models.IPSW{})
|
||||
}
|
||||
|
||||
// Create creates a new entry in the database.
|
||||
// It returns ErrAlreadyExists if the key already exists.
|
||||
func (p Postgres) Create(i *models.IPSW) error {
|
||||
p.db.Create(i)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get returns the value for the given key.
|
||||
// It returns ErrNotFound if the key does not exist.
|
||||
func (p Postgres) Get(key uint) (*models.IPSW, error) {
|
||||
i := &models.IPSW{}
|
||||
p.db.First(&i, key)
|
||||
return i, nil
|
||||
}
|
||||
|
||||
// Set sets the value for the given key.
|
||||
// It overwrites any previous value for that key.
|
||||
func (p Postgres) Set(key uint, value *models.IPSW) error {
|
||||
p.db.Save(value)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete removes the given key.
|
||||
// It returns ErrNotFound if the key does not exist.
|
||||
func (p Postgres) Delete(key uint) error {
|
||||
p.db.Delete(&models.IPSW{}, key)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close closes the database.
|
||||
// It returns ErrClosed if the database is already closed.
|
||||
func (p Postgres) Close() error {
|
||||
db, err := p.db.DB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return db.Close()
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/blacktop/ipsw/internal/models"
|
||||
"github.com/glebarez/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Sqlite is a database that stores data in a sqlite database.
|
||||
type Sqlite struct {
|
||||
URL string
|
||||
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewSqlite creates a new Sqlite database.
|
||||
func NewSqlite(path string) Database {
|
||||
return Postgres{
|
||||
URL: path,
|
||||
}
|
||||
}
|
||||
|
||||
// Connect connects to the database.
|
||||
func (s *Sqlite) Connect() (err error) {
|
||||
s.db, err = gorm.Open(sqlite.Open(s.URL), &gorm.Config{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to connect sqlite database: %w", err)
|
||||
}
|
||||
return s.db.AutoMigrate(&models.IPSW{})
|
||||
}
|
||||
|
||||
// Create creates a new entry in the database.
|
||||
// It returns ErrAlreadyExists if the key already exists.
|
||||
func (s *Sqlite) Create(i *models.IPSW) error {
|
||||
s.db.Create(i)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get returns the value for the given key.
|
||||
// It returns ErrNotFound if the key does not exist.
|
||||
func (s *Sqlite) Get(key uint) (*models.IPSW, error) {
|
||||
i := &models.IPSW{}
|
||||
s.db.First(&i, key)
|
||||
return i, nil
|
||||
}
|
||||
|
||||
// Set sets the value for the given key.
|
||||
// It overwrites any previous value for that key.
|
||||
func (s *Sqlite) Set(key uint, value *models.IPSW) error {
|
||||
s.db.Save(value)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete removes the given key.
|
||||
// It returns ErrNotFound if the key does not exist.
|
||||
func (s *Sqlite) Delete(key uint) error {
|
||||
s.db.Delete(&models.IPSW{}, key)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close closes the database.
|
||||
// It returns ErrClosed if the database is already closed.
|
||||
func (s *Sqlite) Close() error {
|
||||
db, err := s.db.DB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return db.Close()
|
||||
}
|
||||
+37
-33
@@ -269,10 +269,10 @@ func (d *Diff) unmountSystemOsDMGs() error {
|
||||
}
|
||||
|
||||
func (d *Diff) parseKernelcache() error {
|
||||
if err := kernelcache.Extract(d.Old.IPSWPath, d.Old.Folder); err != nil {
|
||||
if _, err := kernelcache.Extract(d.Old.IPSWPath, d.Old.Folder); err != nil {
|
||||
return fmt.Errorf("failed to extract kernelcaches from 'Old' IPSW: %v", err)
|
||||
}
|
||||
if err := kernelcache.Extract(d.New.IPSWPath, d.New.Folder); err != nil {
|
||||
if _, err := kernelcache.Extract(d.New.IPSWPath, d.New.Folder); err != nil {
|
||||
return fmt.Errorf("failed to extract kernelcaches from 'New' IPSW: %v", err)
|
||||
}
|
||||
|
||||
@@ -507,45 +507,49 @@ func (d *Diff) parseEntitlements() (string, error) {
|
||||
}
|
||||
|
||||
func (d *Diff) parseLaunchdPlists() error {
|
||||
oldFsDMG, err := d.Old.Info.GetFileSystemOsDmg()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get 'Old' File System DMG: %v", err)
|
||||
}
|
||||
if err := utils.ExtractFromDMG(d.Old.IPSWPath, oldFsDMG, filepath.Join(d.tmpDir, "old"), regexp.MustCompile(`.*/sbin/launchd$`)); err != nil {
|
||||
return err
|
||||
}
|
||||
m, err := macho.Open(filepath.Join(d.tmpDir, "old/sbin/launchd"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer m.Close()
|
||||
getConfig := func(ctx Context) (string, error) {
|
||||
fsDMG, err := ctx.Info.GetFileSystemOsDmg()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get File System DMG: %v", err)
|
||||
}
|
||||
|
||||
oldData, err := m.Section("__TEXT", "__config").Data()
|
||||
if err != nil {
|
||||
return err
|
||||
extracted, err := utils.ExtractFromDMG(ctx.IPSWPath, fsDMG, d.tmpDir, regexp.MustCompile(`.*/sbin/launchd$`))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to extract launchd from %s: %v", fsDMG, err)
|
||||
}
|
||||
|
||||
if len(extracted) == 0 {
|
||||
return "", fmt.Errorf("failed to extract launchd from %s: no files extracted", fsDMG)
|
||||
} else if len(extracted) > 1 {
|
||||
return "", fmt.Errorf("failed to extract launchd from %s: too many files extracted", fsDMG)
|
||||
}
|
||||
defer os.Remove(filepath.Clean(extracted[0]))
|
||||
|
||||
m, err := macho.Open(extracted[0])
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to open launchd: %v", err)
|
||||
}
|
||||
defer m.Close()
|
||||
|
||||
data, err := m.Section("__TEXT", "__config").Data()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get launchd config: %v", err)
|
||||
}
|
||||
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
newFsDMG, err := d.New.Info.GetFileSystemOsDmg()
|
||||
oldConfig, err := getConfig(d.Old)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get 'New' File System DMG: %v", err)
|
||||
return fmt.Errorf("diff: parseLaunchdPlists: failed to get 'Old' config: %v", err)
|
||||
}
|
||||
if err := utils.ExtractFromDMG(d.New.IPSWPath, newFsDMG, filepath.Join(d.tmpDir, "new"), regexp.MustCompile(`.*/sbin/launchd$`)); err != nil {
|
||||
return err
|
||||
}
|
||||
m2, err := macho.Open(filepath.Join(d.tmpDir, "new/sbin/launchd"))
|
||||
newConfig, err := getConfig(d.New)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("diff: parseLaunchdPlists: failed to get 'New' config: %v", err)
|
||||
}
|
||||
defer m2.Close()
|
||||
|
||||
newData, err := m.Section("__TEXT", "__config").Data()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
out, err := utils.GitDiff(
|
||||
string(oldData)+"\n",
|
||||
string(newData)+"\n",
|
||||
string(oldConfig)+"\n",
|
||||
string(newConfig)+"\n",
|
||||
&utils.GitDiffConfig{Color: false, Tool: "git"})
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
// Package models contains the models for the database.
|
||||
package models
|
||||
|
||||
import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// IPSW is the model for an IPSW file.
|
||||
type IPSW struct {
|
||||
gorm.Model // adds ID, created_at etc.
|
||||
Version string `json:"version"`
|
||||
BuildID string `json:"buildid"`
|
||||
Devices []string `json:"devices"`
|
||||
Date time.Time `json:"date"`
|
||||
Kernels []Kernelcache `json:"kernels"`
|
||||
DSCs []DyldSharedCache `json:"dscs"`
|
||||
Files FileSystem `json:"files"`
|
||||
}
|
||||
|
||||
// Kernelcache is the model for a kernelcache.
|
||||
type Kernelcache struct {
|
||||
gorm.Model
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
// DyldSharedCache is the model for a dyld_shared_cache.
|
||||
type DyldSharedCache struct {
|
||||
gorm.Model
|
||||
Version string `json:"version"`
|
||||
Header string `json:"header"`
|
||||
}
|
||||
|
||||
type FileSystem struct {
|
||||
gorm.Model
|
||||
Files []File `json:"files"`
|
||||
}
|
||||
|
||||
type File struct {
|
||||
gorm.Model
|
||||
Name string `json:"name"`
|
||||
Info os.FileInfo `json:"info"`
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
package store
|
||||
@@ -0,0 +1,31 @@
|
||||
package store
|
||||
|
||||
type Local struct {
|
||||
Folder string
|
||||
}
|
||||
|
||||
func NewLocal(folder string) Store {
|
||||
return Local{
|
||||
Folder: folder,
|
||||
}
|
||||
}
|
||||
|
||||
func (l Local) Connect() error {
|
||||
panic("not implemented") // TODO: Implement
|
||||
}
|
||||
|
||||
func (l Local) Put(key []byte, value []byte) error {
|
||||
panic("not implemented") // TODO: Implement
|
||||
}
|
||||
|
||||
func (l Local) Get(key []byte) ([]byte, error) {
|
||||
panic("not implemented") // TODO: Implement
|
||||
}
|
||||
|
||||
func (l Local) Delete(key []byte) error {
|
||||
panic("not implemented") // TODO: Implement
|
||||
}
|
||||
|
||||
func (l Local) Close() error {
|
||||
panic("not implemented") // TODO: Implement
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
package store
|
||||
@@ -0,0 +1,9 @@
|
||||
package store
|
||||
|
||||
type Store interface {
|
||||
Connect() error
|
||||
Put(key, value []byte) error
|
||||
Get(key []byte) ([]byte, error)
|
||||
Delete(key []byte) error
|
||||
Close() error
|
||||
}
|
||||
+20
-7
@@ -507,6 +507,17 @@ type HdiUtilInfo struct {
|
||||
Images []image `plist:"images,omitempty" xml:"images,omitempty"`
|
||||
}
|
||||
|
||||
func (i HdiUtilInfo) Mount(mount string) *image {
|
||||
for _, img := range i.Images {
|
||||
for _, entry := range img.SystemEntities {
|
||||
if strings.Contains(entry.MountPoint, mount) {
|
||||
return &img
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func MountInfo() (*HdiUtilInfo, error) {
|
||||
if runtime.GOOS == "darwin" {
|
||||
cmd := exec.Command("hdiutil", "info", "-plist")
|
||||
@@ -523,25 +534,25 @@ func MountInfo() (*HdiUtilInfo, error) {
|
||||
return nil, fmt.Errorf("only supported on macOS")
|
||||
}
|
||||
|
||||
func ExtractFromDMG(ipswPath, dmgPath, destPath string, pattern *regexp.Regexp) error {
|
||||
func ExtractFromDMG(ipswPath, dmgPath, destPath string, pattern *regexp.Regexp) ([]string, error) {
|
||||
// check if filesystem DMG already exists (due to previous mount command)
|
||||
if _, err := os.Stat(dmgPath); os.IsNotExist(err) {
|
||||
dmgs, err := Unzip(ipswPath, "", func(f *zip.File) bool {
|
||||
return strings.EqualFold(filepath.Base(f.Name), dmgPath)
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to extract %s from IPSW: %v", dmgPath, err)
|
||||
return nil, fmt.Errorf("failed to extract %s from IPSW: %v", dmgPath, err)
|
||||
}
|
||||
if len(dmgs) == 0 {
|
||||
return fmt.Errorf("failed to find %s in IPSW", dmgPath)
|
||||
return nil, fmt.Errorf("failed to find %s in IPSW", dmgPath)
|
||||
}
|
||||
defer os.Remove(dmgs[0])
|
||||
defer os.Remove(filepath.Clean(dmgs[0]))
|
||||
}
|
||||
|
||||
Indent(log.Info, 2)(fmt.Sprintf("Mounting DMG %s", dmgPath))
|
||||
mountPoint, alreadyMounted, err := MountFS(dmgPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to IPSW FS dmg: %v", err)
|
||||
return nil, fmt.Errorf("failed to IPSW FS dmg: %v", err)
|
||||
}
|
||||
if alreadyMounted {
|
||||
Indent(log.Debug, 3)(fmt.Sprintf("%s already mounted", dmgPath))
|
||||
@@ -556,6 +567,7 @@ func ExtractFromDMG(ipswPath, dmgPath, destPath string, pattern *regexp.Regexp)
|
||||
}()
|
||||
}
|
||||
|
||||
var artifacts []string
|
||||
// extract files that match regex pattern
|
||||
if err := filepath.Walk(mountPoint, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
@@ -574,13 +586,14 @@ func ExtractFromDMG(ipswPath, dmgPath, destPath string, pattern *regexp.Regexp)
|
||||
if err := Copy(path, fname); err != nil {
|
||||
return fmt.Errorf("failed to extract %s: %v", fname, err)
|
||||
}
|
||||
artifacts = append(artifacts, fname)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return fmt.Errorf("failed to extract File System files from IPSW: %v", err)
|
||||
return nil, fmt.Errorf("failed to extract File System files from IPSW: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
return artifacts, nil
|
||||
}
|
||||
|
||||
func PkgUtilExpand(src, dst string) (string, error) {
|
||||
|
||||
+11
-9
@@ -179,13 +179,14 @@ func Verify(sha1sum, name string) (bool, error) {
|
||||
return match, nil
|
||||
}
|
||||
|
||||
// RemoteUnzip unzips a remote file from zip (like partialzip)
|
||||
func RemoteUnzip(files []*zip.File, pattern *regexp.Regexp, folder string, flat, progress bool) error {
|
||||
// SearchZip searches for files in a zip.Reader
|
||||
func SearchZip(files []*zip.File, pattern *regexp.Regexp, folder string, flat, progress bool) ([]string, error) {
|
||||
var fname string
|
||||
var artifacts []string
|
||||
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get current working directory: %v", err)
|
||||
return nil, fmt.Errorf("failed to get current working directory: %v", err)
|
||||
}
|
||||
|
||||
found := false
|
||||
@@ -202,21 +203,21 @@ func RemoteUnzip(files []*zip.File, pattern *regexp.Regexp, folder string, flat,
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(filepath.Dir(fname), 0750); err != nil {
|
||||
return fmt.Errorf("failed to create directory %s: %v", filepath.Dir(fname), err)
|
||||
return nil, fmt.Errorf("failed to create directory %s: %v", filepath.Dir(fname), err)
|
||||
}
|
||||
|
||||
var r io.ReadCloser
|
||||
if _, err := os.Stat(fname); os.IsNotExist(err) {
|
||||
rc, err := f.Open()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error opening remote zipped file %s: %v", f.Name, err)
|
||||
return nil, fmt.Errorf("error opening remote zipped file %s: %v", f.Name, err)
|
||||
}
|
||||
defer rc.Close()
|
||||
|
||||
var p *mpb.Progress
|
||||
if progress {
|
||||
// setup progress bar
|
||||
var total int64 = int64(f.UncompressedSize64)
|
||||
var total = int64(f.UncompressedSize64)
|
||||
p = mpb.New(
|
||||
mpb.WithWidth(60),
|
||||
mpb.WithRefreshRate(180*time.Millisecond),
|
||||
@@ -242,7 +243,7 @@ func RemoteUnzip(files []*zip.File, pattern *regexp.Regexp, folder string, flat,
|
||||
Indent(log.Info, 2)(fmt.Sprintf("Extracting %s", strings.TrimPrefix(fname, cwd)))
|
||||
out, err := os.Create(fname)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating remote unzipped file destination %s: %v", fname, err)
|
||||
return nil, fmt.Errorf("error creating remote unzipped file destination %s: %v", fname, err)
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
@@ -253,6 +254,7 @@ func RemoteUnzip(files []*zip.File, pattern *regexp.Regexp, folder string, flat,
|
||||
p.Wait()
|
||||
}
|
||||
|
||||
artifacts = append(artifacts, fname)
|
||||
} else {
|
||||
Indent(log.Warn, 2)(fmt.Sprintf("%s already exists", fname))
|
||||
}
|
||||
@@ -260,10 +262,10 @@ func RemoteUnzip(files []*zip.File, pattern *regexp.Regexp, folder string, flat,
|
||||
}
|
||||
|
||||
if !found {
|
||||
return fmt.Errorf("no files found matching %s", pattern.String())
|
||||
return nil, fmt.Errorf("no files found matching %s", pattern.String())
|
||||
}
|
||||
|
||||
return nil
|
||||
return artifacts, nil
|
||||
}
|
||||
|
||||
// Unzip - https://stackoverflow.com/a/24792688
|
||||
|
||||
+21
-19
@@ -46,13 +46,13 @@ func GetDscPathsInMount(mountPoint string) ([]string, error) {
|
||||
return matches, nil
|
||||
}
|
||||
|
||||
func ExtractFromDMG(i *info.Info, dmgPath, destPath string, arches []string) error {
|
||||
func ExtractFromDMG(i *info.Info, dmgPath, destPath string, arches []string) ([]string, error) {
|
||||
|
||||
utils.Indent(log.Info, 2)(fmt.Sprintf("Mounting DMG %s", dmgPath))
|
||||
var alreadyMounted bool
|
||||
mountPoint, alreadyMounted, err := utils.MountFS(dmgPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to IPSW FS dmg: %v", err)
|
||||
return nil, fmt.Errorf("failed to IPSW FS dmg: %v", err)
|
||||
}
|
||||
if alreadyMounted {
|
||||
utils.Indent(log.Debug, 3)(fmt.Sprintf("%s already mounted", dmgPath))
|
||||
@@ -69,23 +69,23 @@ func ExtractFromDMG(i *info.Info, dmgPath, destPath string, arches []string) err
|
||||
|
||||
if runtime.GOOS == "darwin" {
|
||||
if err := os.MkdirAll(destPath, 0750); err != nil {
|
||||
return fmt.Errorf("failed to create destination directory %s: %v", destPath, err)
|
||||
return nil, fmt.Errorf("failed to create destination directory %s: %v", destPath, err)
|
||||
}
|
||||
} else {
|
||||
if _, ok := os.LookupEnv("IPSW_IN_DOCKER"); ok {
|
||||
if err := os.MkdirAll(filepath.Join("/data", destPath), 0750); err != nil {
|
||||
return fmt.Errorf("failed to create destination directory %s: %v", destPath, err)
|
||||
return nil, fmt.Errorf("failed to create destination directory %s: %v", destPath, err)
|
||||
}
|
||||
} else {
|
||||
if err := os.MkdirAll(destPath, 0750); err != nil {
|
||||
return fmt.Errorf("failed to create destination directory %s: %v", destPath, err)
|
||||
return nil, fmt.Errorf("failed to create destination directory %s: %v", destPath, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
matches, err := GetDscPathsInMount(mountPoint)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if utils.StrSliceContains(i.Plists.BuildManifest.SupportedProductTypes, "mac") { // Is macOS IPSW
|
||||
@@ -99,9 +99,9 @@ func ExtractFromDMG(i *info.Info, dmgPath, destPath string, arches []string) err
|
||||
if err := survey.AskOne(prompt, &selMatches); err != nil {
|
||||
if err == terminal.InterruptErr {
|
||||
log.Warn("Exiting...")
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
matches = selMatches
|
||||
} else {
|
||||
@@ -114,44 +114,46 @@ func ExtractFromDMG(i *info.Info, dmgPath, destPath string, arches []string) err
|
||||
}
|
||||
|
||||
if len(filtered) == 0 {
|
||||
return fmt.Errorf("no dyld_shared_cache files found matching the specified archs: %v", arches)
|
||||
return nil, fmt.Errorf("no dyld_shared_cache files found matching the specified archs: %v", arches)
|
||||
}
|
||||
matches = filtered
|
||||
}
|
||||
}
|
||||
|
||||
if len(matches) == 0 {
|
||||
return fmt.Errorf("failed to find dyld_shared_cache(s) in DMG: %s", dmgPath)
|
||||
return nil, fmt.Errorf("failed to find dyld_shared_cache(s) in DMG: %s", dmgPath)
|
||||
}
|
||||
|
||||
var artifacts []string
|
||||
for _, match := range matches {
|
||||
dyldDest := filepath.Join(destPath, filepath.Base(match))
|
||||
utils.Indent(log.Info, 3)(fmt.Sprintf("Extracting %s to %s", filepath.Base(match), dyldDest))
|
||||
if err := utils.Copy(match, dyldDest); err != nil {
|
||||
return fmt.Errorf("failed to copy %s to %s: %v", match, dyldDest, err)
|
||||
return nil, fmt.Errorf("failed to copy %s to %s: %v", match, dyldDest, err)
|
||||
}
|
||||
artifacts = append(artifacts, dyldDest)
|
||||
}
|
||||
|
||||
return nil
|
||||
return artifacts, nil
|
||||
}
|
||||
|
||||
// Extract extracts dyld_shared_cache from IPSW
|
||||
func Extract(ipsw, destPath string, arches []string) error {
|
||||
func Extract(ipsw, destPath string, arches []string) ([]string, error) {
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
return fmt.Errorf("dyld extraction is not supported on Windows (see github.com/blacktop/go-apfs)")
|
||||
return nil, fmt.Errorf("dyld extraction is not supported on Windows (see github.com/blacktop/go-apfs)")
|
||||
}
|
||||
|
||||
i, err := info.Parse(ipsw)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse IPSW: %v", err)
|
||||
return nil, fmt.Errorf("failed to parse IPSW: %v", err)
|
||||
}
|
||||
|
||||
dmgPath, err := i.GetSystemOsDmg()
|
||||
if err != nil {
|
||||
dmgPath, err = i.GetFileSystemOsDmg()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get File System DMG: %v", err)
|
||||
return nil, fmt.Errorf("failed to get File System DMG: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,10 +163,10 @@ func Extract(ipsw, destPath string, arches []string) error {
|
||||
return strings.EqualFold(filepath.Base(f.Name), dmgPath)
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to extract %s from IPSW: %v", dmgPath, err)
|
||||
return nil, fmt.Errorf("failed to extract %s from IPSW: %v", dmgPath, err)
|
||||
}
|
||||
if len(dmgs) == 0 {
|
||||
return fmt.Errorf("File System %s NOT found in IPSW", dmgPath)
|
||||
return nil, fmt.Errorf("File System %s NOT found in IPSW", dmgPath)
|
||||
}
|
||||
defer os.Remove(dmgs[0])
|
||||
}
|
||||
@@ -234,7 +236,7 @@ func ExtractFromRemoteCryptex(zr *zip.Reader, destPath string, arches []string)
|
||||
return fmt.Errorf("failed to parse info from cryptex-system-arm64e: %v", err)
|
||||
}
|
||||
|
||||
if err := ExtractFromDMG(i, out.Name(), destPath, arches); err != nil {
|
||||
if _, err := ExtractFromDMG(i, out.Name(), destPath, arches); err != nil {
|
||||
return fmt.Errorf("failed to extract dyld_shared_cache from cryptex-system-arm64e: %v", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -94,51 +94,52 @@ func ParseImg4Data(data []byte) (*CompressedCache, error) {
|
||||
}
|
||||
|
||||
// Extract extracts and decompresses a kernelcache from ipsw
|
||||
func Extract(ipsw, destPath string) error {
|
||||
log.Debug("Extracting Kernelcache from IPSW")
|
||||
func Extract(ipsw, destPath string) ([]string, error) {
|
||||
kcaches, err := utils.Unzip(ipsw, "", func(f *zip.File) bool {
|
||||
return strings.Contains(f.Name, "kernelcache")
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed extract kernelcache from ipsw")
|
||||
return nil, fmt.Errorf("failed to unzip kernelcache: %v", err)
|
||||
}
|
||||
|
||||
i, err := info.Parse(ipsw)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse ipsw info: %v", err)
|
||||
return nil, fmt.Errorf("failed to parse ipsw info: %v", err)
|
||||
}
|
||||
|
||||
var artifacts []string
|
||||
for _, kcache := range kcaches {
|
||||
fname := i.GetKernelCacheFileName(kcache)
|
||||
// fname := fmt.Sprintf("%s.%s", strings.TrimSuffix(kcache, filepath.Ext(kcache)), strings.Join(i.GetDevicesForKernelCache(kcache), "_"))
|
||||
fname = filepath.Join(destPath, fname)
|
||||
fname = filepath.Clean(fname)
|
||||
|
||||
content, err := os.ReadFile(kcache)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to read Kernelcache")
|
||||
return nil, errors.Wrap(err, "failed to read Kernelcache")
|
||||
}
|
||||
|
||||
kc, err := ParseImg4Data(content)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed parse compressed kernelcache Img4")
|
||||
return nil, fmt.Errorf("failed to parse im4p kernelcache data: %v", err)
|
||||
}
|
||||
|
||||
dec, err := DecompressData(kc)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to decompress kernelcache")
|
||||
return nil, fmt.Errorf("failed to decompress kernelcache data: %v", err)
|
||||
}
|
||||
|
||||
os.Mkdir(destPath, 0750)
|
||||
|
||||
err = os.WriteFile(fname, dec, 0660)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to write kernelcache")
|
||||
if err := os.MkdirAll(filepath.Dir(fname), 0750); err != nil {
|
||||
return nil, fmt.Errorf("failed to create output directory: %v", err)
|
||||
}
|
||||
if err := os.WriteFile(fname, dec, 0660); err != nil {
|
||||
return nil, fmt.Errorf("failed to write decompressed kernelcache: %v", err)
|
||||
}
|
||||
utils.Indent(log.Info, 2)("Created " + fname)
|
||||
os.Remove(kcache)
|
||||
|
||||
artifacts = append(artifacts, fname)
|
||||
}
|
||||
|
||||
return nil
|
||||
return artifacts, nil
|
||||
}
|
||||
|
||||
// Decompress decompresses a compressed kernelcache
|
||||
@@ -295,11 +296,12 @@ func DecompressData(cc *CompressedCache) ([]byte, error) {
|
||||
}
|
||||
|
||||
// RemoteParse parses plist files in a remote ipsw file
|
||||
func RemoteParse(zr *zip.Reader, destPath string) error {
|
||||
func RemoteParse(zr *zip.Reader, destPath string) ([]string, error) {
|
||||
var artifacts []string
|
||||
|
||||
i, err := info.ParseZipFiles(zr.File)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, f := range zr.File {
|
||||
@@ -309,35 +311,36 @@ func RemoteParse(zr *zip.Reader, destPath string) error {
|
||||
kdata := make([]byte, f.UncompressedSize64)
|
||||
rc, err := f.Open()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open kernelcache %s in zip: %v", f.Name, err)
|
||||
return nil, fmt.Errorf("failed to open kernelcache %s in zip: %v", f.Name, err)
|
||||
}
|
||||
io.ReadFull(rc, kdata)
|
||||
rc.Close()
|
||||
|
||||
kcomp, err := ParseImg4Data(kdata)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse kernelcache im4p %s: %v", f.Name, err)
|
||||
return nil, fmt.Errorf("failed to parse kernelcache im4p %s: %v", f.Name, err)
|
||||
}
|
||||
|
||||
dec, err := DecompressData(kcomp)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decompress kernelcache %s: %v", f.Name, err)
|
||||
return nil, fmt.Errorf("failed to decompress kernelcache %s: %v", f.Name, err)
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(filepath.Dir(fname), 0750); err != nil {
|
||||
return fmt.Errorf("failed to create destination directory: %v", err)
|
||||
return nil, fmt.Errorf("failed to create destination directory: %v", err)
|
||||
}
|
||||
utils.Indent(log.Info, 2)(fmt.Sprintf("Writing %s", fname))
|
||||
if err := os.WriteFile(fname, dec, 0660); err != nil {
|
||||
return fmt.Errorf("failed to write kernelcache %s: %v", fname, err)
|
||||
return nil, fmt.Errorf("failed to write kernelcache %s: %v", fname, err)
|
||||
}
|
||||
artifacts = append(artifacts, fname)
|
||||
} else {
|
||||
log.Warnf("kernelcache already exists: %s", fname)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return artifacts, nil
|
||||
}
|
||||
|
||||
// Parse parses the compressed kernelcache Img4 data
|
||||
|
||||
+36
-27
@@ -147,22 +147,8 @@ func GetKextInfos(m *macho.File) ([]KmodInfoT, error) {
|
||||
return nil, fmt.Errorf("section __PRELINK_INFO.__kmod_start not found")
|
||||
}
|
||||
|
||||
// KextList lists all the kernel extensions in the kernelcache
|
||||
func KextList(kernel string, diffable bool) ([]string, error) {
|
||||
var out []string
|
||||
|
||||
m, err := macho.Open(kernel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer m.Close()
|
||||
|
||||
kextStartAdddrs, err := GetKextStartVMAddrs(m)
|
||||
if err != nil {
|
||||
log.Debugf("failed to get kext start addresses: %v", err)
|
||||
}
|
||||
|
||||
if infoSec := m.Section("__PRELINK_INFO", "__info"); infoSec != nil {
|
||||
func GetKexts(kernel *macho.File) ([]CFBundle, error) {
|
||||
if infoSec := kernel.Section("__PRELINK_INFO", "__info"); infoSec != nil {
|
||||
|
||||
data, err := infoSec.Data()
|
||||
if err != nil {
|
||||
@@ -175,18 +161,41 @@ func KextList(kernel string, diffable bool) ([]string, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode __PRELINK_INFO.__info section: %v", err)
|
||||
}
|
||||
return prelink.PrelinkInfoDictionary, nil
|
||||
}
|
||||
return nil, fmt.Errorf("section __PRELINK_INFO.__info not found")
|
||||
}
|
||||
|
||||
if diffable {
|
||||
for _, bundle := range prelink.PrelinkInfoDictionary {
|
||||
out = append(out, fmt.Sprintf("%s (%s)", bundle.ID, bundle.Version))
|
||||
}
|
||||
} else {
|
||||
for _, bundle := range prelink.PrelinkInfoDictionary {
|
||||
if !bundle.OSKernelResource && len(kextStartAdddrs) > 0 {
|
||||
out = append(out, fmt.Sprintf("%#x: %s (%s)", kextStartAdddrs[bundle.ModuleIndex]|tagPtrMask, bundle.ID, bundle.Version))
|
||||
} else {
|
||||
out = append(out, fmt.Sprintf("%#x: %s (%s)", bundle.ExecutableLoadAddr, bundle.ID, bundle.Version))
|
||||
}
|
||||
// KextList lists all the kernel extensions in the kernelcache
|
||||
func KextList(kernelPath string, diffable bool) ([]string, error) {
|
||||
var out []string
|
||||
|
||||
m, err := macho.Open(kernelPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer m.Close()
|
||||
|
||||
bundles, err := GetKexts(m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
kextStartAdddrs, err := GetKextStartVMAddrs(m)
|
||||
if err != nil {
|
||||
log.Debugf("failed to get kext start addresses: %v", err)
|
||||
}
|
||||
|
||||
if diffable {
|
||||
for _, bundle := range bundles {
|
||||
out = append(out, fmt.Sprintf("%s (%s)", bundle.ID, bundle.Version))
|
||||
}
|
||||
} else {
|
||||
for _, bundle := range bundles {
|
||||
if !bundle.OSKernelResource && len(kextStartAdddrs) > 0 {
|
||||
out = append(out, fmt.Sprintf("%#x: %s (%s)", kextStartAdddrs[bundle.ModuleIndex]|tagPtrMask, bundle.ID, bundle.Version))
|
||||
} else {
|
||||
out = append(out, fmt.Sprintf("%#x: %s (%s)", bundle.ExecutableLoadAddr, bundle.ID, bundle.Version))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,18 +81,18 @@ type sysMaster struct {
|
||||
}
|
||||
|
||||
type sysent struct {
|
||||
Call uint64 // implementing function
|
||||
Munge uint64 // system call arguments munger for 32-bit process
|
||||
ReturnType returnType // system call return types
|
||||
NArg int16 // number of args
|
||||
ArgBytes uint16 // Total size of arguments in bytes for 32-bit system calls
|
||||
Call uint64 `json:"call,omitempty"` // implementing function
|
||||
Munge uint64 `json:"munge,omitempty"` // system call arguments munger for 32-bit process
|
||||
ReturnType returnType `json:"return_type,omitempty"` // system call return types
|
||||
NArg int16 `json:"n_arg,omitempty"` // number of args
|
||||
ArgBytes uint16 `json:"arg_bytes,omitempty"` // Total size of arguments in bytes for 32-bit system calls
|
||||
}
|
||||
|
||||
type Sysent struct {
|
||||
Name string
|
||||
Number int
|
||||
Proto string
|
||||
New bool
|
||||
Name string `json:"name,omitempty"`
|
||||
Number int `json:"number,omitempty"`
|
||||
Proto string `json:"proto,omitempty"`
|
||||
New bool `json:"new,omitempty"`
|
||||
sysent
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -405,7 +405,7 @@ func Extract(otaZIP, extractPattern, folder string) error {
|
||||
|
||||
// check for matches in the OTA zip
|
||||
utils.Indent(log.Info, 2)("Searching in OTA zip files...")
|
||||
if err := utils.RemoteUnzip(zr.File, re, folder, false, false); err != nil {
|
||||
if _, err := utils.SearchZip(zr.File, re, folder, false, false); err != nil {
|
||||
log.Errorf("failed to find in OTA zip: %v", err)
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -151,7 +151,7 @@ func Parse(path string) (*Plists, error) {
|
||||
|
||||
zr, err := zip.OpenReader(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open ipsw zip: %s", err)
|
||||
return nil, fmt.Errorf("failed to open IPSW zip: %s", err)
|
||||
}
|
||||
defer zr.Close()
|
||||
|
||||
|
||||
+3
-5
@@ -6,10 +6,9 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/jinzhu/gorm"
|
||||
// importing the sqlite dialects
|
||||
_ "github.com/jinzhu/gorm/dialects/sqlite"
|
||||
"github.com/glebarez/sqlite"
|
||||
"github.com/pkg/errors"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func uniqueDevices(d []Device) []Device {
|
||||
@@ -35,11 +34,10 @@ func ReadDeviceTraitsDB() ([]Device, error) {
|
||||
for _, releaseType := range []string{"", "-beta"} {
|
||||
dbFile := fmt.Sprintf("/Applications/Xcode%s.app/Contents/Developer/Platforms/%s.platform/usr/standalone/device_traits.db", releaseType, osType)
|
||||
if _, err := os.Stat(dbFile); err == nil {
|
||||
db, err := gorm.Open("sqlite3", dbFile)
|
||||
db, err := gorm.Open(sqlite.Open(dbFile), &gorm.Config{})
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "unable to open database: %s", dbFile)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
var devices []Device
|
||||
db.Preload("DeviceTrait").Find(&devices)
|
||||
|
||||
@@ -58,6 +58,28 @@ const config = {
|
||||
},
|
||||
}),
|
||||
],
|
||||
[
|
||||
"redocusaurus",
|
||||
/** @type {import('redocusaurus').PresetEntry} */
|
||||
({
|
||||
// Plugin Options for loading OpenAPI files
|
||||
specs: [
|
||||
{
|
||||
spec: "api/swagger.json",
|
||||
route: "/api",
|
||||
layout: {
|
||||
title: "ipsw API",
|
||||
noFooter: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
// Theme Options for modifying how redoc renders them
|
||||
theme: {
|
||||
// Change with your site colors
|
||||
primaryColor: "#503B9F",
|
||||
},
|
||||
}),
|
||||
],
|
||||
],
|
||||
// plugins: [require.resolve("@cmfcmf/docusaurus-search-local")],
|
||||
themeConfig:
|
||||
@@ -113,6 +135,12 @@ const config = {
|
||||
sidebarId: "cli",
|
||||
label: "CLI",
|
||||
},
|
||||
{
|
||||
label: "API",
|
||||
to: "/api",
|
||||
position: "left",
|
||||
sidebarId: "api",
|
||||
},
|
||||
// Right
|
||||
{
|
||||
href: "https://github.com/blacktop/ipsw",
|
||||
|
||||
Generated
+1032
-1
File diff suppressed because it is too large
Load Diff
+2
-1
@@ -25,7 +25,8 @@
|
||||
"prism-react-renderer": "^1.3.5",
|
||||
"prism-themes": "^1.9.0",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2"
|
||||
"react-dom": "^17.0.2",
|
||||
"redocusaurus": "^1.6.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/eslint-plugin": "2.4.0",
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# NOTE: Only supports options marked as "Supported in Redoc CE"
|
||||
# See https://redocly.com/docs/cli/configuration/ for more information.
|
||||
|
||||
extends:
|
||||
- recommended
|
||||
|
||||
rules:
|
||||
no-unused-components: error
|
||||
|
||||
theme:
|
||||
# See https://redocly.com/docs/api-reference-docs/configuration/functionality/
|
||||
openapi:
|
||||
disableSearch: true
|
||||
# See https://redocly.com/docs/api-reference-docs/configuration/theming/
|
||||
theme:
|
||||
colors:
|
||||
primary:
|
||||
main: "#503B9F"
|
||||
@@ -0,0 +1,582 @@
|
||||
{
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"schemes": [
|
||||
"http"
|
||||
],
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"title": "The ipsw API.",
|
||||
"version": "v1.0"
|
||||
},
|
||||
"host": "localhost:8080",
|
||||
"basePath": "/v1",
|
||||
"paths": {
|
||||
"/_ping": {
|
||||
"get": {
|
||||
"description": "This will return \"OK\" if the daemon is running.",
|
||||
"tags": [
|
||||
"Daemon"
|
||||
],
|
||||
"summary": "Ping",
|
||||
"operationId": "getDaemonPing"
|
||||
},
|
||||
"head": {
|
||||
"description": "This will return if 200 the daemon is running.",
|
||||
"tags": [
|
||||
"Daemon"
|
||||
],
|
||||
"summary": "Ping",
|
||||
"operationId": "headDaemonPing"
|
||||
}
|
||||
},
|
||||
"/device_list": {
|
||||
"get": {
|
||||
"description": "This will return JSON of all XCode devices.",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"DeviceList"
|
||||
],
|
||||
"summary": "List XCode Devices.",
|
||||
"operationId": "getDeviceList"
|
||||
}
|
||||
},
|
||||
"/download/ipsw/ios/latest/build": {
|
||||
"get": {
|
||||
"description": "Get latest iOS build.",
|
||||
"tags": [
|
||||
"Download"
|
||||
],
|
||||
"summary": "Latest iOS Build",
|
||||
"operationId": "getDownloadLatestIPSWsBuild"
|
||||
}
|
||||
},
|
||||
"/download/ipsw/ios/latest/version": {
|
||||
"get": {
|
||||
"description": "Get latest iOS version.",
|
||||
"tags": [
|
||||
"Download"
|
||||
],
|
||||
"summary": "Latest iOS Version",
|
||||
"operationId": "getDownloadLatestIPSWsVersion"
|
||||
}
|
||||
},
|
||||
"/dsc/imports": {
|
||||
"get": {
|
||||
"description": "Get list of dylibs that import a given dylib.",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"DSC"
|
||||
],
|
||||
"summary": "Imports",
|
||||
"operationId": "getDscImports",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "path to IPSW",
|
||||
"name": "path",
|
||||
"in": "query",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "dylib to search for",
|
||||
"name": "dylib",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/dsc/info": {
|
||||
"get": {
|
||||
"description": "Get info about a given DSC",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"DSC"
|
||||
],
|
||||
"summary": "Info",
|
||||
"operationId": "getDscInfo",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "path to IPSW",
|
||||
"name": "path",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/dsc/macho": {
|
||||
"get": {
|
||||
"description": "Get MachO info for a given dylib in the DSC.",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"DSC"
|
||||
],
|
||||
"summary": "MachO",
|
||||
"operationId": "getDscMacho",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "path to IPSW",
|
||||
"name": "path",
|
||||
"in": "query",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "dylib to search for",
|
||||
"name": "dylib",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/dsc/str": {
|
||||
"get": {
|
||||
"description": "Get strings in the DSC that match a given pattern.",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"DSC"
|
||||
],
|
||||
"summary": "Strings",
|
||||
"operationId": "getDscStrings",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "path to IPSW",
|
||||
"name": "path",
|
||||
"in": "query",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "regex to search for",
|
||||
"name": "pattern",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/dsc/symaddr": {
|
||||
"get": {
|
||||
"description": "Get symbols addresses in the DSC that match a given lookup JSON payload.",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"DSC"
|
||||
],
|
||||
"summary": "Symbols",
|
||||
"operationId": "getDscSymbols",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "path to IPSW",
|
||||
"name": "path",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/extract/dmg": {
|
||||
"get": {
|
||||
"description": "Extract DMGs from an IPSW.",
|
||||
"tags": [
|
||||
"Extract"
|
||||
],
|
||||
"summary": "DMG",
|
||||
"operationId": "getExtractDmg"
|
||||
}
|
||||
},
|
||||
"/extract/dsc": {
|
||||
"get": {
|
||||
"description": "Extract dyld_shared_caches from an IPSW.",
|
||||
"tags": [
|
||||
"Extract"
|
||||
],
|
||||
"summary": "DSC",
|
||||
"operationId": "getExtractDsc"
|
||||
}
|
||||
},
|
||||
"/extract/kbag": {
|
||||
"get": {
|
||||
"description": "Extract KBAGs from an IPSW.",
|
||||
"tags": [
|
||||
"Extract"
|
||||
],
|
||||
"summary": "KBAG",
|
||||
"operationId": "getExtractKbags"
|
||||
}
|
||||
},
|
||||
"/extract/kernel": {
|
||||
"get": {
|
||||
"description": "Extract kernelcaches from an IPSW.",
|
||||
"tags": [
|
||||
"Extract"
|
||||
],
|
||||
"summary": "Kernel",
|
||||
"operationId": "getExtractKernel"
|
||||
}
|
||||
},
|
||||
"/extract/pattern": {
|
||||
"get": {
|
||||
"description": "Extract files from an IPSW that match a given pattern.",
|
||||
"tags": [
|
||||
"Extract"
|
||||
],
|
||||
"summary": "Pattern",
|
||||
"operationId": "getExtractPattern"
|
||||
}
|
||||
},
|
||||
"/idev/info": {
|
||||
"get": {
|
||||
"description": "Get info about USB connected devices.",
|
||||
"tags": [
|
||||
"USB"
|
||||
],
|
||||
"summary": "Info",
|
||||
"operationId": "getIdevInfo"
|
||||
}
|
||||
},
|
||||
"/info/ipsw": {
|
||||
"get": {
|
||||
"description": "Get IPSW info.",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Info"
|
||||
],
|
||||
"summary": "IPSW",
|
||||
"operationId": "getIpswInfo",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "path to IPSW",
|
||||
"name": "path",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/info/ipsw/remote": {
|
||||
"get": {
|
||||
"description": "Get remote IPSW info.",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Info"
|
||||
],
|
||||
"summary": "Remote IPSW",
|
||||
"operationId": "getRemoteIpswInfo",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "url to IPSW",
|
||||
"name": "url",
|
||||
"in": "query",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "http proxy to use",
|
||||
"name": "proxy",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"description": "ignore TLS errors",
|
||||
"name": "insecure",
|
||||
"in": "query"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/info/ota": {
|
||||
"get": {
|
||||
"description": "Get OTA info.",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Info"
|
||||
],
|
||||
"summary": "OTA",
|
||||
"operationId": "getOtaInfo",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "path to OTA",
|
||||
"name": "path",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/info/ota/remote": {
|
||||
"get": {
|
||||
"description": "Get remote OTA info.",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Info"
|
||||
],
|
||||
"summary": "Remote OTA",
|
||||
"operationId": "getRemoteOtaInfo",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "url to OTA",
|
||||
"name": "url",
|
||||
"in": "query",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "http proxy to use",
|
||||
"name": "proxy",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"description": "ignore TLS errors",
|
||||
"name": "insecure",
|
||||
"in": "query"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/ipsw/fs/ents": {
|
||||
"get": {
|
||||
"description": "Get IPSW Filesystem DMG MachO entitlements.",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"IPSW"
|
||||
],
|
||||
"summary": "Entitlements",
|
||||
"operationId": "getIpswFsEntitlements",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "path to IPSW",
|
||||
"name": "path",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/ipsw/fs/files": {
|
||||
"get": {
|
||||
"description": "Get IPSW Filesystem DMG file listing.",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"IPSW"
|
||||
],
|
||||
"summary": "Files",
|
||||
"operationId": "getIpswFsFiles",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "path to IPSW",
|
||||
"name": "path",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/kernel/kexts": {
|
||||
"get": {
|
||||
"description": "Get kernelcache KEXTs info.",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Kernel"
|
||||
],
|
||||
"summary": "Kexts",
|
||||
"operationId": "getKernelKexts",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "path to kernelcache",
|
||||
"name": "path",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/kernel/syscall": {
|
||||
"get": {
|
||||
"description": "Get kernelcache syscalls info.",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Kernel"
|
||||
],
|
||||
"summary": "Syscalls",
|
||||
"operationId": "getKernelSyscalls",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "path to kernelcache",
|
||||
"name": "path",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/kernel/version": {
|
||||
"get": {
|
||||
"description": "Get kernelcache version.",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Kernel"
|
||||
],
|
||||
"summary": "Version",
|
||||
"operationId": "getKernelVersion",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "path to kernelcache",
|
||||
"name": "path",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/macho/info": {
|
||||
"get": {
|
||||
"description": "Get MachO info.",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"MachO"
|
||||
],
|
||||
"summary": "Info",
|
||||
"operationId": "getMachoInfo",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "path to MachO",
|
||||
"name": "path",
|
||||
"in": "query",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "architecture to get info for in universal MachO",
|
||||
"name": "arch",
|
||||
"in": "query"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/mount/{type}": {
|
||||
"post": {
|
||||
"description": "Mount a DMG inside a given IPSW.",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Mount"
|
||||
],
|
||||
"summary": "Mount",
|
||||
"operationId": "postMount",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "type of DMG to mount",
|
||||
"name": "type",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "path to IPSW",
|
||||
"name": "path",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/unmount": {
|
||||
"post": {
|
||||
"description": "Unmount a previously mounted DMG.",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Mount"
|
||||
],
|
||||
"summary": "Unmount",
|
||||
"operationId": "postUnmount",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "mount point of DMG",
|
||||
"name": "mount_point",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "path to DMG",
|
||||
"name": "dmg_path",
|
||||
"in": "query"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/version": {
|
||||
"get": {
|
||||
"description": "This will return the daemon version info.",
|
||||
"tags": [
|
||||
"Daemon"
|
||||
],
|
||||
"summary": "Version",
|
||||
"operationId": "getDaemonVersion"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user