This file is indexed.

/usr/lib/python2.7/dist-packages/postr/flickrest.py is in postr 0.13.1-1.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  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
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
# flickrpc -- a Flickr client library.
#
# Copyright (C) 2007 Ross Burton <ross@burtonini.com>
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 2, 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 Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
# St, Fifth Floor, Boston, MA 02110-1301 USA

import logging, os, mimetools, urllib
import gio
from twisted.internet import defer
from twisted.python.failure import Failure
import proxyclient as client

try:
    from hashlib import md5
except ImportError:
    from md5 import md5

try:
    from xml.etree import ElementTree
except ImportError:
    from elementtree import ElementTree

class FlickrError(Exception):
    def __init__(self, code, message):
        Exception.__init__(self)
        self.code = int(code)
        self.message = message
    
    def __str__(self):
        return "%d: %s" % (self.code, self.message)

(SIZE_SQUARE,
 SIZE_THUMB,
 SIZE_SMALL,
 SIZE_MEDIUM,
 SIZE_LARGE) = range (0, 5)

class Flickr:
    endpoint = "https://api.flickr.com/services/rest/?"
    
    def __init__(self, api_key, secret, perms="read"):
        self.__methods = {}
        self.api_key = api_key
        self.secret = secret
        self.perms = perms
        self.token = None
        self.logger = logging.getLogger('flickrest')
        self.set_proxy(os.environ.get("http_proxy", None))
        self.fullname = None
        self.username = None
        self.nsid = None

    def get_fullname(self):
        return self.fullname

    def get_username(self):
        return self.username

    def get_nsid(self):
        return self.nsid

    def set_proxy(self, proxy):
        # Handle proxies which are not URLs
        if proxy and "://" not in proxy:
            proxy = "https://" + proxy
        self.proxy = proxy
    
    def __repr__(self):
        return "<FlickREST>"
    
    def __getTokenFile(self):
        """Get the filename that contains the authentication token for the API key"""
        return os.path.expanduser(os.path.join("~", ".flickr", self.api_key, "auth.xml"))

    def clear_cached(self):
        """Remove any cached information on disk."""
        self.fullname = None
        self.username = None
        self.nsid = None
        token = self.__getTokenFile()
        if os.path.exists(token):
            os.remove(token)
        self.token = None
    
    def __sign(self, kwargs):
        kwargs['api_key'] = self.api_key
        # If authenticating we don't yet have a token
        if self.token:
            kwargs['auth_token'] = self.token
        # I know this is less efficient than working with lists, but this is
        # much more readable.
        sig = reduce(lambda sig, key: sig + key + str(kwargs[key]),
                     sorted(kwargs.keys()),
                     self.secret)
        kwargs['api_sig'] = md5(sig).hexdigest()

    def __call(self, method, kwargs):
        kwargs["method"] = method
        self.__sign(kwargs)
        self.logger.info("Calling %s" % method)
        return client.getPage(Flickr.endpoint, proxy=self.proxy, method="POST",
                              headers={"Content-Type": "application/x-www-form-urlencoded"},
                              postdata=urllib.urlencode(kwargs))
    
    def __cb(self, data, method):
        self.logger.info("%s returned" % method)
        xml = ElementTree.XML(data)
        if xml.tag == "rsp" and xml.get("stat") == "ok":
            return xml
        elif xml.tag == "rsp" and xml.get("stat") == "fail":
            err = xml.find("err")
            raise FlickrError(err.get("code"), err.get("msg"))
        else:
            # Fake an error in this case
            raise FlickrError(0, "Invalid response")
    
    def __getattr__(self, method):
        method = "flickr." + method.replace("_", ".")
        if not self.__methods.has_key(method):
            def proxy(method=method, **kwargs):
                return self.__call(method, kwargs).addCallback(self.__cb, method)
            self.__methods[method] = proxy
        return self.__methods[method]

    @staticmethod
    def __encodeForm(inputs):
        """
        Takes a dict of inputs and returns a multipart/form-data string
        containing the utf-8 encoded data. Keys must be strings, values
        can be either strings or file-like objects.
        """
        boundary = mimetools.choose_boundary()
        lines = []
        for key, val in inputs.items():
            lines.append("--" + boundary.encode("utf-8"))
            header = 'Content-Disposition: form-data; name="%s";' % key
            if isinstance(val, gio.File):
                header += 'filename="%s";' % val.get_basename()
                lines.append(header)
                header = "Content-Type: application/octet-stream"
            lines.append(header)
            lines.append("")
            if isinstance(val, gio.File):
                contents, length, etags = val.load_contents()
                lines.append(contents)
            # Otherwise just hope it is string-like and encode it to
            # UTF-8. TODO: this breaks when val is binary data.
            else:
                # GtkEntry seems to always return utf-8
                lines.append(str(val))
        # Add final boundary.
        lines.append("--" + boundary.encode("utf-8"))
        return (boundary, '\r\n'.join(lines))
    
    def upload(self, uri=None, imageData=None,
               title=None, desc=None, tags=None,
               is_public=None, is_family=None, is_friend=None,
               safety=None, search_hidden=None, content_type=None,
               progress_tracker=None):
        # Sanity check the arguments
        if uri is None and imageData is None:
            raise ValueError("Need to pass either uri or imageData")
        if uri and imageData:
            raise ValueError("Cannot pass both uri and imageData")

        kwargs = {}
        if title:
            kwargs['title'] = title
        if desc:
            kwargs['description'] = desc
        if tags:
            kwargs['tags'] = tags
        if is_public is not None:
            kwargs['is_public'] = is_public and 1 or 0
        if is_family is not None:
            kwargs['is_family'] = is_family and 1 or 0
        if is_friend is not None:
            kwargs['is_friend'] = is_friend and 1 or 0
        if safety:
            kwargs['safety_level'] = safety
        if search_hidden is not None:
            kwargs['hidden'] = search_hidden and 2 or 1 # Why Flickr, why?
        if content_type:
            kwargs['content_type'] = content_type
        self.__sign(kwargs)
        self.logger.info("Upload args %s" % kwargs)
        
        if imageData:
            kwargs['photo'] = imageData
        else:
            kwargs['photo'] = gio.File(uri)

        (boundary, form) = self.__encodeForm(kwargs)
        headers= {
            "Content-Type": "multipart/form-data; boundary=%s" % boundary,
            "Content-Length": str(len(form))
            }

        self.logger.info("Calling upload")
        return client.upload("https://api.flickr.com/services/upload/",
                             proxy=self.proxy, method="POST",
                             headers=headers, postdata=form,
                             progress_tracker=progress_tracker).addCallback(self.__cb, "upload")

    def authenticate_2(self, state):
        def gotToken(e):
            # Set the token
            self.token = e.find("auth/token").text

            # Pulling out the user information
            user = e.find("auth/user")
            # Setting the user variables
            self.fullname = user.get("fullname")
            self.username = user.get("username")
            self.nsid = user.get("nsid")

            # Cache the authentication
            filename = self.__getTokenFile()
            path = os.path.dirname(filename)
            if not os.path.exists(path):
                os.makedirs(path, 0700)
            f = file(filename, "w")
            f.write(ElementTree.tostring(e))
            f.close()

            # Callback to the user
            return True
        return self.auth_getToken(frob=state['frob']).addCallback(gotToken)

    def __get_frob(self):
        """Make the getFrob() call."""
        def gotFrob(xml):
            frob = xml.find("frob").text
            keys = { 'perms': self.perms,
                     'frob': frob }
            self.__sign(keys)
            url = "https://flickr.com/services/auth/?api_key=%(api_key)s&perms=%(perms)s&frob=%(frob)s&api_sig=%(api_sig)s" % keys
            return {'url': url, 'frob': frob}
        return self.auth_getFrob().addCallback(gotFrob)

    def authenticate_1(self):
        """Attempts to log in to Flickr. The return value is a Twisted Deferred
        object that callbacks when the first part of the authentication is
        completed.  If the result passed to the deferred callback is None, then
        the required authentication was locally cached and you are
        authenticated.  Otherwise the result is a dictionary, you should open
        the URL specified by the 'url' key and instruct the user to follow the
        instructions.  Once that is done, pass the state to
        flickrest.authenticate_2()."""

        filename = self.__getTokenFile()
        if os.path.exists(filename):
            try:
                e = ElementTree.parse(filename).getroot()
                self.token = e.find("auth/token").text
                
                user = e.find("auth/user")
                self.fullname = user.get("fullname")
                self.username = user.get("username")
                self.nsid = user.get("nsid")

                def reply(xml):
                    return defer.succeed(None)
                def failed(failure):
                    # If checkToken() failed, we need to re-authenticate
                    self.clear_cached()
                    return self.__get_frob()
                return self.auth_checkToken().addCallbacks(reply, failed)
            except:
                # TODO: print the exception to stderr?
                pass
            
        return self.__get_frob()
    
    @staticmethod
    def get_photo_url(photo, size=SIZE_MEDIUM):
        if photo is None:
            return None

        # Handle medium as the default
        suffix = ""
        if size == SIZE_SQUARE:
            suffix = "_s"
        elif size == SIZE_THUMB:
            suffix = "_t"
        elif size == SIZE_SMALL:
            suffix = "_m"
        elif size == SIZE_LARGE:
            suffix = "_b"

        return "https://static.flickr.com/%s/%s_%s%s.jpg" % (photo.get("server"), photo.get("id"), photo.get("secret"), suffix)