|
a/src/mediaserver/cdplugins/uprcl/uprclhttp.py |
|
b/src/mediaserver/cdplugins/uprcl/uprclhttp.py |
|
... |
|
... |
21 |
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
21 |
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
22 |
from __future__ import print_function
|
22 |
from __future__ import print_function
|
23 |
|
23 |
|
24 |
import SocketServer
|
24 |
import SocketServer
|
25 |
import BaseHTTPServer
|
25 |
import BaseHTTPServer
|
26 |
import SimpleHTTPServer
|
|
|
27 |
import os
|
26 |
import os
|
28 |
import posixpath
|
27 |
import posixpath
|
29 |
import BaseHTTPServer
|
|
|
30 |
import urllib
|
28 |
import urllib
|
31 |
import cgi
|
29 |
import urlparse
|
32 |
import shutil
|
30 |
import shutil
|
33 |
import mimetypes
|
31 |
import mimetypes
|
34 |
import sys
|
32 |
import sys
|
|
|
33 |
|
|
|
34 |
import mutagen
|
35 |
|
35 |
|
36 |
try:
|
36 |
try:
|
37 |
from cStringIO import StringIO
|
37 |
from cStringIO import StringIO
|
38 |
except ImportError:
|
38 |
except ImportError:
|
39 |
from StringIO import StringIO
|
39 |
from StringIO import StringIO
|
40 |
|
40 |
|
41 |
from uprclutils import uplog
|
41 |
from uprclutils import uplog,printable
|
42 |
|
|
|
43 |
|
42 |
|
44 |
|
43 |
|
45 |
__version__ = "0.1"
|
44 |
__version__ = "0.1"
|
46 |
|
45 |
|
47 |
class RangeHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
|
46 |
class RangeHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
|
|
... |
|
... |
93 |
to the outputfile by the caller unless the command was HEAD,
|
92 |
to the outputfile by the caller unless the command was HEAD,
|
94 |
and must be closed by the caller under all circumstances), or
|
93 |
and must be closed by the caller under all circumstances), or
|
95 |
None, in which case the caller has nothing further to do.
|
94 |
None, in which case the caller has nothing further to do.
|
96 |
|
95 |
|
97 |
"""
|
96 |
"""
|
98 |
uplog("HTTP: path: %s" % self.path)
|
97 |
|
99 |
path = self.translate_path(self.path)
|
98 |
path,embedded = self.translate_path(self.path)
|
100 |
uplog("HTTP: translated path: %s" % urllib.quote(path))
|
99 |
#uplog("HTTP: translated: embedded %s path: %s" %
|
|
|
100 |
# (embedded, printable(path)))
|
|
|
101 |
|
101 |
if not path or not os.path.exists(path):
|
102 |
if not path or not os.path.exists(path):
|
102 |
self.send_error(404)
|
103 |
self.send_error(404)
|
103 |
return (None, 0, 0)
|
104 |
return (None, 0, 0)
|
104 |
|
105 |
|
105 |
if not os.path.isfile(path):
|
106 |
if not os.path.isfile(path):
|
106 |
self.send_error(405)
|
107 |
self.send_error(405)
|
107 |
return (None, 0, 0)
|
108 |
return (None, 0, 0)
|
108 |
|
109 |
|
109 |
f = None
|
110 |
f = None
|
110 |
ctype = self.guess_type(path)
|
|
|
111 |
try:
|
111 |
try:
|
|
|
112 |
if embedded:
|
|
|
113 |
ctype, size, f = self.embedded_open(path)
|
|
|
114 |
fs = os.stat(path)
|
|
|
115 |
#uplog("embedded, got ctype %s size %s" %(ctype, size))
|
|
|
116 |
else:
|
|
|
117 |
ctype = self.guess_type(path)
|
112 |
f = open(path, 'rb')
|
118 |
f = open(path, 'rb')
|
113 |
except:
|
119 |
fs = os.fstat(f.fileno())
|
|
|
120 |
size = int(fs[6])
|
|
|
121 |
except Exception as err:
|
114 |
self.send_error(404, "File not found")
|
122 |
self.send_error(404, "File not found")
|
115 |
return (None, 0, 0)
|
123 |
return (None, 0, 0)
|
116 |
|
124 |
|
117 |
if "Range" in self.headers:
|
125 |
if "Range" in self.headers:
|
118 |
self.send_response(206)
|
126 |
self.send_response(206)
|
119 |
else:
|
127 |
else:
|
120 |
self.send_response(200)
|
128 |
self.send_response(200)
|
121 |
|
129 |
|
|
|
130 |
self.send_header("Last-Modified", self.date_time_string(fs.st_mtime))
|
122 |
self.send_header("Content-type", ctype)
|
131 |
self.send_header("Content-type", ctype)
|
123 |
fs = os.fstat(f.fileno())
|
|
|
124 |
size = int(fs[6])
|
|
|
125 |
start_range = 0
|
132 |
start_range = 0
|
126 |
end_range = size
|
133 |
end_range = size
|
127 |
self.send_header("Accept-Ranges", "bytes")
|
134 |
self.send_header("Accept-Ranges", "bytes")
|
128 |
if "Range" in self.headers:
|
135 |
if "Range" in self.headers:
|
129 |
s, e = self.headers['range'][6:].split('-', 1)
|
136 |
s, e = self.headers['range'][6:].split('-', 1)
|
|
... |
|
... |
139 |
start_range = size - ei
|
146 |
start_range = size - ei
|
140 |
self.send_header("Content-Range",
|
147 |
self.send_header("Content-Range",
|
141 |
'bytes ' + str(start_range) + '-' +
|
148 |
'bytes ' + str(start_range) + '-' +
|
142 |
str(end_range - 1) + '/' + str(size))
|
149 |
str(end_range - 1) + '/' + str(size))
|
143 |
self.send_header("Content-Length", end_range - start_range)
|
150 |
self.send_header("Content-Length", end_range - start_range)
|
144 |
self.send_header("Last-Modified", self.date_time_string(fs.st_mtime))
|
|
|
145 |
self.end_headers()
|
151 |
self.end_headers()
|
146 |
#uplog("Sending Bytes %d to %d" % (start_range, end_range))
|
152 |
#uplog("Sending Bytes %d to %d" % (start_range, end_range))
|
147 |
return (f, start_range, end_range)
|
153 |
return (f, start_range, end_range)
|
148 |
|
154 |
|
149 |
|
155 |
|
150 |
def translate_path(self, opath):
|
156 |
def translate_path(self, opath):
|
151 |
path = urllib.unquote(opath)
|
157 |
path = urllib.unquote(opath)
|
152 |
path = path.replace(self.uprclpathprefix, '', 1)
|
158 |
path = path.replace(self.uprclpathprefix, '', 1)
|
|
|
159 |
|
|
|
160 |
q = urlparse.urlparse(path)
|
|
|
161 |
path = q.path
|
|
|
162 |
embedded = False
|
|
|
163 |
pq = urlparse.parse_qs(q.query)
|
|
|
164 |
if 'embed' in pq:
|
|
|
165 |
embedded = True
|
|
|
166 |
|
153 |
for fsp,htp in self.uprclpathmap.iteritems():
|
167 |
for fsp,htp in self.uprclpathmap.iteritems():
|
154 |
if path.startswith(fsp):
|
168 |
if path.startswith(fsp):
|
155 |
return path.replace(fsp, htp, 1)
|
169 |
path = path.replace(fsp, htp, 1)
|
|
|
170 |
if embedded:
|
|
|
171 |
# Embedded image urls have had a .jpg or .png
|
|
|
172 |
# appended. Remove it to restore the track path
|
|
|
173 |
# name.
|
|
|
174 |
i = path.rfind('.')
|
|
|
175 |
path = path[:i]
|
|
|
176 |
return path, embedded
|
156 |
|
177 |
|
|
|
178 |
# Security feature here: never allow access to anything not in
|
|
|
179 |
# the path map
|
157 |
uplog("HTTP: translate_path: %s not found in path map" % opath)
|
180 |
uplog("HTTP: translate_path: %s not found in path map" % opath)
|
158 |
return None
|
181 |
return None, None
|
|
|
182 |
|
|
|
183 |
|
|
|
184 |
# Open embedded image. Returns mtype, size, f
|
|
|
185 |
def embedded_open(self, path):
|
|
|
186 |
try:
|
|
|
187 |
mutf = mutagen.File(path)
|
|
|
188 |
except Exception as err:
|
|
|
189 |
raise err
|
|
|
190 |
|
|
|
191 |
f = None
|
|
|
192 |
size = 0
|
|
|
193 |
if 'audio/mp3' in mutf.mime:
|
|
|
194 |
for tagname in mutf.iterkeys():
|
|
|
195 |
if tagname.startswith('APIC:'):
|
|
|
196 |
#self.em.rclog("mp3 img: %s" % mutf[tagname].mime)
|
|
|
197 |
mtype = mutf[tagname].mime
|
|
|
198 |
s = mutf[tagname].data
|
|
|
199 |
size = len(s)
|
|
|
200 |
f = StringIO(s)
|
|
|
201 |
elif 'audio/x-flac' in mutf.mime:
|
|
|
202 |
if mutf.pictures:
|
|
|
203 |
mtype = mutf.pictures[0].mime
|
|
|
204 |
size = len(mutf.pictures[0].data)
|
|
|
205 |
f = StringIO(mutf.pictures[0].data)
|
|
|
206 |
elif 'audio/mp4' in mutf.mime:
|
|
|
207 |
if 'covr' in mutf.iterkeys():
|
|
|
208 |
format = mutf['covr'][0].imageformat
|
|
|
209 |
if format == mutagen.mp4.AtomDataType.JPEG:
|
|
|
210 |
mtype = 'image/jpeg'
|
|
|
211 |
else:
|
|
|
212 |
mtype = 'image/png'
|
|
|
213 |
size = len(mutf['covr'][0])
|
|
|
214 |
f = StringIO(mutf['covr'][0])
|
|
|
215 |
if f is None:
|
|
|
216 |
raise Exception("can't open embedded image")
|
|
|
217 |
else:
|
|
|
218 |
return mtype, size, f
|
159 |
|
219 |
|
160 |
def guess_type(self, path):
|
220 |
def guess_type(self, path):
|
161 |
"""Guess the type of a file.
|
221 |
"""Guess the type of a file.
|
162 |
|
222 |
|
163 |
Argument is a PATH (a filename).
|
223 |
Argument is a PATH (a filename).
|