Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

# 

# INTERFACE TO GNUPG PACKAGE 

# 

# Package for reading and writing text files that may or may not be encrypted. 

# File will be encrypted if file path ends in a GPG extension. 

 

# License {{{1 

# Copyright (C) 2016 Kenneth S. Kundert 

# 

# This program is free software: you can redistribute it and/or modify it under 

# the terms of the GNU General Public License as published by the Free Software 

# Foundation, either version 3 of the License, or (at your option) any later 

# version. 

# 

# This program is distributed in the hope that it will be useful, but WITHOUT 

# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 

# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 

# details. 

# 

# You should have received a copy of the GNU General Public License along with 

# this program. If not, see http://www.gnu.org/licenses/. 

 

 

# Imports {{{1 

from .config import get_setting, override_setting 

from shlib import to_path, mv, mkdir 

from inform import ( 

conjoin, cull, display, Error, log, narrate, os_error, warn, is_str, full_stop 

) 

import gnupg 

try: 

from StringIO import StringIO # python2 

except ImportError: 

from io import StringIO # python3 

 

# Globals {{{1 

GPG_EXTENSIONS = ['.gpg', '.asc'] 

ARMOR_CHOICES = ['always', 'never', 'extension'] 

ActiveFile= None 

 

# get_active_file() {{{1 

def get_active_file(): 

return ActiveFile 

 

# GnuPG class {{{1 

class GnuPG(object): 

def __init__(self, path): 

self.path = to_path(path) 

 

@classmethod 

def initialize(cls, 

gpg_path=None, gpg_home=None, armor=None 

): 

from .config import get_setting, override_setting 

 

cls.gpg_path = to_path( 

gpg_path if gpg_path else get_setting('gpg_executable') 

) 

override_setting('gpg_executable', cls.gpg_path) 

 

cls.gpg_home = to_path( 

gpg_home if gpg_home else get_setting('gpg_home') 

) 

override_setting('gpg_home', cls.gpg_home) 

 

armor = armor if armor is not None else get_setting('gpg_armor') 

if armor not in ARMOR_CHOICES: 

warn( 

"'%s' is not valid, choose from %s." % ( 

armor, conjoin(ARMOR_CHOICES) 

), culprit=(get_setting('config_file'), 'gpg_armor') 

) 

armor = None 

cls.armor = armor 

override_setting('gpg_armor', armor) 

 

gpg_args = {} 

if cls.gpg_path: 

gpg_args.update({'gpgbinary': str(cls.gpg_path)}) 

if cls.gpg_home: 

gpg_args.update({'gnupghome': str(cls.gpg_home)}) 

cls.gpg = gnupg.GPG(**gpg_args) 

 

def save(self, contents, gpg_ids=None): 

path = self.path 

if not gpg_ids: 

gpg_ids = get_setting('gpg_ids', []) 

if is_str(gpg_ids): 

gpg_ids = gpg_ids.split() 

if not gpg_ids: 

raise Error('must specify GPG ID.') 

 

use_gpg, use_armor = self._choices() 

if use_gpg: 

try: 

encoded = contents.encode(get_setting('encoding')) 

encrypted = self.gpg.encrypt(encoded, gpg_ids, armor=use_armor) 

if not encrypted.ok: 

msg = ' '.join(cull([ 

'unable to encrypt.', 

getattr(encrypted, 'stderr', None) 

])) 

raise Error(msg, culprit=path, sep='\n') 

else: 

path.write_bytes(encrypted.data) 

except ValueError as err: 

raise Error(str(err), culprit=path) 

else: 

path.write_text(contents, encoding=get_setting('encoding')) 

path.chmod(0o600) 

 

def read(self): 

path = self.path 

# file is only assumed to be encrypted if path has gpg extension 

if path.suffix.lower() in GPG_EXTENSIONS: 

try: 

with path.open('rb') as f: 

decrypted = self.gpg.decrypt_file(f) 

if not decrypted.ok: 

msg = ' '.join(cull([ 

'unable to decrypt.', 

getattr(decrypted, 'stderr', None) 

])) 

raise Error(msg, culprit=path, sep='\n') 

except ValueError as err: 

raise Error(str(err), culprit=path) 

except (IOError, OSError) as err: 

raise Error(os_error(err)) 

return decrypted.data.decode(get_setting('encoding')) 

else: 

return path.read_text(encoding=get_setting('encoding')) 

 

def _choices(self): 

extension = self.path.suffix.lower() 

if extension in GPG_EXTENSIONS: 

if self.armor == 'never': 

return True, False 

elif self.armor != 'always' and extension == '.gpg': 

return True, False 

return True, True 

else: 

return False, False 

 

def remove(self): 

self.path.unlink() 

 

def rename(self, extension): 

new = to_path(str(self.path) + extension) 

mv(self.path, new) 

self.path = new 

 

def will_encrypt(self): 

return self.path.suffix in GPG_EXTENSIONS 

 

 

# BufferedFile class {{{1 

class BufferedFile(GnuPG): 

def __init__(self, path, ignore_errors_on_close=False): 

# file will only be encrypted if path has gpg extension 

self.path = path 

self.stream = StringIO() 

self.ignore_errors_on_close = ignore_errors_on_close 

 

def write(self, content): 

self.stream.write(content) 

 

def flush(self): 

pass 

 

def close(self): 

 

if get_setting('discard_logfile'): 

# this is the case when the output would be uninteresting (such as 

# with help messages) and running GPG (and thus risk requiring the 

# user to type in the passphrase) is silly. 

return 

contents = self.stream.getvalue() 

try: 

self.save(contents, get_setting('gpg_ids')) 

except Error: 

if not self.ignore_errors_on_close: 

raise 

 

 

# PythonFile class {{{1 

class PythonFile(GnuPG): 

def run(self): 

global ActiveFile 

ActiveFile = self.path 

path = self.path 

self.encrypted = path.suffix in ['.gpg', '.asc'] 

log('reading.', culprit=path) 

try: 

self.code = self.read() 

# need to save the code for the new command 

except OSError as err: 

raise Error(os_error(err)) 

 

try: 

compiled = compile(self.code, str(path), 'exec') 

except SyntaxError as err: 

culprit = (err.filename, err.lineno) 

if err.text is None or err.offset is None: 

raise Error(full_stop(err.msg), culprit=culprit) 

else: 

raise Error( 

err.msg + ':', err.text, (err.offset-1)*' ' + '^', 

culprit=culprit, sep='\n' 

) 

# File "/home/ken/.config/avendesora/config", line 18 

# 'c': 'google-chrome %s' 

# ^ 

 

contents = {} 

exec(compiled, contents) 

ActiveFile = None 

return contents 

 

def create(self, contents, gpg_ids): 

path = self.path 

try: 

mkdir(get_setting('settings_dir')) 

if path.exists(): 

# file creation (init) requested, but file already exists 

# don't overwrite the file, instead read it so the information  

# can be used to create any remaining files. 

display("%s: already exists." % path) 

return 

# create the file 

display('%s: creating.' % path) 

if path.suffix in ['.gpg', '.asc']: 

narrate('encrypting.', culprit=path) 

# encrypt it 

self.save(contents, gpg_ids) 

else: 

narrate('not encrypting.', culprit=path) 

# file is not encrypted 

with path.open('w') as f: 

f.write(contents.encode(get_setting('encoding'))) 

except OSError as err: 

raise Error(os_error(err)) 

 

def __str__(self): 

return str(self.path)