1 #!/usr/bin/env python 2 # 3 # Copyright 2006, 2007 Google Inc. All Rights Reserved. 4 # Author: danderson (at] google.com (David Anderson) 5 # 6 # Script for uploading files to a Google Code project. 7 # 8 # This is intended to be both a useful script for people who want to 9 # streamline project uploads and a reference implementation for 10 # uploading files to Google Code projects. 11 # 12 # To upload a file to Google Code, you need to provide a path to the 13 # file on your local machine, a small summary of what the file is, a 14 # project name, and a valid account that is a member or owner of that 15 # project. You can optionally provide a list of labels that apply to 16 # the file. The file will be uploaded under the same name that it has 17 # in your local filesystem (that is, the "basename" or last path 18 # component). Run the script with '--help' to get the exact syntax 19 # and available options. 20 # 21 # Note that the upload script requests that you enter your 22 # googlecode.com password. This is NOT your Gmail account password! 23 # This is the password you use on googlecode.com for committing to 24 # Subversion and uploading files. You can find your password by going 25 # to http://code.google.com/hosting/settings when logged in with your 26 # Gmail account. If you have already committed to your project's 27 # Subversion repository, the script will automatically retrieve your 28 # credentials from there (unless disabled, see the output of '--help' 29 # for details). 30 # 31 # If you are looking at this script as a reference for implementing 32 # your own Google Code file uploader, then you should take a look at 33 # the upload() function, which is the meat of the uploader. You 34 # basically need to build a multipart/form-data POST request with the 35 # right fields and send it to https://PROJECT.googlecode.com/files . 36 # Authenticate the request using HTTP Basic authentication, as is 37 # shown below. 38 # 39 # Licensed under the terms of the Apache Software License 2.0: 40 # http://www.apache.org/licenses/LICENSE-2.0 41 # 42 # Questions, comments, feature requests and patches are most welcome. 43 # Please direct all of these to the Google Code users group: 44 # http://groups.google.com/group/google-code-hosting 45 # 46 # Licensed under the Apache License, Version 2.0 (the "License"); 47 # you may not use this file except in compliance with the License. 48 # You may obtain a copy of the License at 49 # 50 # http://www.apache.org/licenses/LICENSE-2.0 51 52 """Google Code file uploader script. 53 """ 54 55 __author__ = 'danderson (at] google.com (David Anderson)' 56 57 import httplib 58 import os.path 59 import optparse 60 import getpass 61 import base64 62 import sys 63 64 65 def upload(file, project_name, user_name, password, summary, labels=None): 66 """Upload a file to a Google Code project's file server. 67 68 Args: 69 file: The local path to the file. 70 project_name: The name of your project on Google Code. 71 user_name: Your Google account name. 72 password: The googlecode.com password for your account. 73 Note that this is NOT your global Google Account password! 74 summary: A small description for the file. 75 labels: an optional list of label strings with which to tag the file. 76 77 Returns: a tuple: 78 http_status: 201 if the upload succeeded, something else if an 79 error occured. 80 http_reason: The human-readable string associated with http_status 81 file_url: If the upload succeeded, the URL of the file on Google 82 Code, None otherwise. 83 """ 84 # The login is the user part of user (at] gmail.com. If the login provided 85 # is in the full user@domain form, strip it down. 86 if user_name.endswith('@gmail.com'): 87 user_name = user_name[:user_name.index('@gmail.com')] 88 89 form_fields = [('summary', summary)] 90 if labels is not None: 91 form_fields.extend([('label', l.strip()) for l in labels]) 92 93 content_type, body = encode_upload_request(form_fields, file) 94 95 upload_host = '%s.googlecode.com' % project_name 96 upload_uri = '/files' 97 auth_token = base64.b64encode('%s:%s'% (user_name, password)) 98 headers = { 99 'Authorization': 'Basic %s' % auth_token, 100 'User-Agent': 'Googlecode.com uploader v0.9.4', 101 'Content-Type': content_type, 102 } 103 104 server = httplib.HTTPSConnection(upload_host) 105 server.request('POST', upload_uri, body, headers) 106 resp = server.getresponse() 107 server.close() 108 109 if resp.status == 201: 110 location = resp.getheader('Location', None) 111 else: 112 location = None 113 return resp.status, resp.reason, location 114 115 116 def encode_upload_request(fields, file_path): 117 """Encode the given fields and file into a multipart form body. 118 119 fields is a sequence of (name, value) pairs. file is the path of 120 the file to upload. The file will be uploaded to Google Code with 121 the same file name. 122 123 Returns: (content_type, body) ready for httplib.HTTP instance 124 """ 125 BOUNDARY = '----------Googlecode_boundary_reindeer_flotilla' 126 CRLF = '\r\n' 127 128 body = [] 129 130 # Add the metadata about the upload first 131 for key, value in fields: 132 body.extend( 133 ['--' + BOUNDARY, 134 'Content-Disposition: form-data; name="%s"' % key, 135 '', 136 value, 137 ]) 138 139 # Now add the file itself 140 file_name = os.path.basename(file_path) 141 f = open(file_path, 'rb') 142 file_content = f.read() 143 f.close() 144 145 body.extend( 146 ['--' + BOUNDARY, 147 'Content-Disposition: form-data; name="filename"; filename="%s"' 148 % file_name, 149 # The upload server determines the mime-type, no need to set it. 150 'Content-Type: application/octet-stream', 151 '', 152 file_content, 153 ]) 154 155 # Finalize the form body 156 body.extend(['--' + BOUNDARY + '--', '']) 157 158 return 'multipart/form-data; boundary=%s' % BOUNDARY, CRLF.join(body) 159 160 161 def upload_find_auth(file_path, project_name, summary, labels=None, 162 user_name=None, password=None, tries=3): 163 """Find credentials and upload a file to a Google Code project's file server. 164 165 file_path, project_name, summary, and labels are passed as-is to upload. 166 167 Args: 168 file_path: The local path to the file. 169 project_name: The name of your project on Google Code. 170 summary: A small description for the file. 171 labels: an optional list of label strings with which to tag the file. 172 config_dir: Path to Subversion configuration directory, 'none', or None. 173 user_name: Your Google account name. 174 tries: How many attempts to make. 175 """ 176 if user_name is None or password is None: 177 from netrc import netrc 178 # Chromium edit: Works on windows without requiring HOME to be set. 179 netrc_path = os.path.join(os.path.expanduser('~'), '.netrc') 180 authenticators = netrc(netrc_path).authenticators("code.google.com") 181 if authenticators: 182 if user_name is None: 183 user_name = authenticators[0] 184 if password is None: 185 password = authenticators[2] 186 187 if user_name is None or password is None: 188 raise RuntimeError('Missing user credentials for upload') 189 190 return upload(file_path, project_name, user_name, password, summary, labels) 191 192 193 def main(): 194 parser = optparse.OptionParser(usage='googlecode-upload.py -s SUMMARY ' 195 '-p PROJECT [options] FILE') 196 parser.add_option('-s', '--summary', dest='summary', 197 help='Short description of the file') 198 parser.add_option('-p', '--project', dest='project', 199 help='Google Code project name') 200 parser.add_option('-u', '--user', dest='user', 201 help='Your Google Code username') 202 parser.add_option('-w', '--password', dest='password', 203 help='Your Google Code password') 204 parser.add_option('-l', '--labels', dest='labels', 205 help='An optional list of comma-separated labels to attach ' 206 'to the file') 207 208 options, args = parser.parse_args() 209 210 if not options.summary: 211 parser.error('File summary is missing.') 212 elif not options.project: 213 parser.error('Project name is missing.') 214 elif len(args) < 1: 215 parser.error('File to upload not provided.') 216 elif len(args) > 1: 217 parser.error('Only one file may be specified.') 218 219 file_path = args[0] 220 221 if options.labels: 222 labels = options.labels.split(',') 223 else: 224 labels = None 225 226 status, reason, url = upload_find_auth(file_path, options.project, 227 options.summary, labels, 228 options.user, options.password) 229 if url: 230 print 'The file was uploaded successfully.' 231 print 'URL: %s' % url 232 return 0 233 else: 234 print 'An error occurred. Your file was not uploaded.' 235 print 'Google Code upload server said: %s (%s)' % (reason, status) 236 return 1 237 238 239 if __name__ == '__main__': 240 sys.exit(main()) 241